allocator tests
This commit is contained in:
@@ -12,75 +12,8 @@
|
||||
|
||||
#include "wfc_utils.hpp"
|
||||
|
||||
#define WFC_USE_STACK_ALLOCATOR
|
||||
|
||||
inline void* allocate_aligned_memory(size_t alignment, size_t size) {
|
||||
#ifdef WFC_USE_STACK_ALLOCATOR
|
||||
void* ptr = nullptr;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
ptr = _aligned_malloc(size, alignment);
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#if __cplusplus >= 201703L
|
||||
ptr = std::aligned_alloc(alignment, size);
|
||||
#else
|
||||
#ifdef POSIX_MEMALIGN
|
||||
posix_memalign(&ptr, alignment, size);
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#if __cplusplus >= 201703L
|
||||
ptr = std::aligned_alloc(alignment, size);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return ptr;
|
||||
#else
|
||||
// When not using stack allocator, use standard malloc with manual alignment
|
||||
void* ptr = std::malloc(size + alignment - 1 + sizeof(void*));
|
||||
if (!ptr) return nullptr;
|
||||
|
||||
void* aligned_ptr = static_cast<char*>(ptr) + sizeof(void*) +
|
||||
(alignment - (reinterpret_cast<uintptr_t>(static_cast<char*>(ptr) + sizeof(void*)) % alignment)) % alignment;
|
||||
|
||||
// Store original pointer for free
|
||||
*(static_cast<void**>(aligned_ptr) - 1) = ptr;
|
||||
|
||||
return aligned_ptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void free_aligned_memory(void* ptr) {
|
||||
if (!ptr) return;
|
||||
|
||||
#ifdef WFC_USE_STACK_ALLOCATOR
|
||||
#ifdef _MSC_VER
|
||||
_aligned_free(ptr);
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#if __cplusplus >= 201703L
|
||||
std::free(ptr);
|
||||
#else
|
||||
#ifdef POSIX_MEMALIGN
|
||||
free(ptr);
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#if __cplusplus >= 201703L
|
||||
std::free(ptr);
|
||||
#else
|
||||
free(ptr);
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
// When not using stack allocator, free the original pointer
|
||||
void* original_ptr = *(static_cast<void**>(ptr) - 1);
|
||||
std::free(original_ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace WFC {
|
||||
|
||||
#ifdef WFC_USE_STACK_ALLOCATOR
|
||||
/**
|
||||
* @brief Stack allocator specifically designed for WFC branching operations
|
||||
*
|
||||
@@ -99,16 +32,6 @@ private:
|
||||
MemoryPool(void* p, size_t s) : ptr(p), size(s), used(0) {}
|
||||
};
|
||||
|
||||
struct Block {
|
||||
void* ptr;
|
||||
size_t size;
|
||||
size_t alignment;
|
||||
size_t poolIndex; // Which pool this allocation came from
|
||||
|
||||
Block() : ptr(nullptr), size(0), alignment(0), poolIndex(0) {}
|
||||
Block(void* p, size_t s, size_t a, size_t pi) : ptr(p), size(s), alignment(a), poolIndex(pi) {}
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct allocator with initial capacity
|
||||
@@ -116,14 +39,22 @@ public:
|
||||
*/
|
||||
explicit WFCStackAllocator(size_t initialCapacity = 1024 * 1024) // 1MB default
|
||||
{
|
||||
addPool(0); // first pool is 0
|
||||
addPool(initialCapacity);
|
||||
m_currentPoolIndex = 1;
|
||||
}
|
||||
|
||||
explicit WFCStackAllocator(std::span<uint8_t> userGivenData)
|
||||
{
|
||||
m_pools.push_back(MemoryPool(userGivenData.data(), userGivenData.size()));
|
||||
m_currentPoolIndex = 0;
|
||||
}
|
||||
|
||||
~WFCStackAllocator() {
|
||||
for (auto& pool : m_pools) {
|
||||
if (pool.ptr) {
|
||||
free_aligned_memory(pool.ptr);
|
||||
}
|
||||
// first pool is not deallocated because it's either empty or comes from the user
|
||||
for (size_t i = 1; i < m_pools.size(); ++i)
|
||||
{
|
||||
delete[] static_cast<char*>(m_pools[i].ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,55 +64,31 @@ public:
|
||||
WFCStackAllocator(WFCStackAllocator&&) = delete;
|
||||
WFCStackAllocator& operator=(WFCStackAllocator&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Allocate memory from the stack
|
||||
* @param size Number of bytes to allocate
|
||||
* @param alignment Memory alignment requirement (default 8)
|
||||
* @return Pointer to allocated memory
|
||||
*/
|
||||
void* allocate(size_t size, size_t alignment = 8) {
|
||||
// Try to allocate from existing pools
|
||||
for (size_t i = 0; i < m_pools.size(); ++i) {
|
||||
auto& pool = m_pools[i];
|
||||
|
||||
// Align the current position in this pool
|
||||
size_t alignedUsed = alignUp(pool.used, alignment);
|
||||
|
||||
// Check if we have enough space in this pool
|
||||
if (alignedUsed + size <= pool.size) {
|
||||
void* ptr = static_cast<char*>(pool.ptr) + alignedUsed;
|
||||
pool.used = alignedUsed + size;
|
||||
|
||||
// Track this allocation for proper cleanup
|
||||
m_allocations.emplace_back(ptr, size, alignment, i);
|
||||
void* allocate(size_t size)
|
||||
{
|
||||
size = alignUp(size);
|
||||
|
||||
for (; m_currentPoolIndex < m_pools.size(); ++m_currentPoolIndex)
|
||||
{
|
||||
if (getCapacity() >= size)
|
||||
{
|
||||
auto& pool = m_pools[m_currentPoolIndex];
|
||||
void* ptr = static_cast<char*>(pool.ptr) + pool.used;
|
||||
pool.used += size;
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// No existing pool has enough space, add a new one
|
||||
size_t newPoolSize = std::max(m_pools.back().size * 2, size * 2); // Grow exponentially
|
||||
addPool(newPoolSize);
|
||||
|
||||
// Now allocate from the new pool (which is the last one)
|
||||
auto& newPool = m_pools.back();
|
||||
void* ptr = static_cast<char*>(newPool.ptr);
|
||||
newPool.used = size;
|
||||
|
||||
// Track this allocation for proper cleanup
|
||||
m_allocations.emplace_back(ptr, size, alignment, m_pools.size() - 1);
|
||||
|
||||
return ptr;
|
||||
return allocate(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deallocate memory (no-op as requested - memory freed when branch goes out of scope)
|
||||
* @param ptr Pointer to deallocate
|
||||
*/
|
||||
void deallocate(void*) {
|
||||
// No-op as requested - deallocation happens when branch goes out of scope
|
||||
// The stack nature ensures automatic cleanup
|
||||
}
|
||||
void deallocate(void*)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Create a stack frame marker for RAII-based cleanup
|
||||
@@ -190,40 +97,25 @@ public:
|
||||
class StackFrame {
|
||||
private:
|
||||
WFCStackAllocator& m_allocator;
|
||||
std::vector<size_t> m_savedUsed;
|
||||
size_t m_savedAllocCount;
|
||||
size_t m_poolIndex{};
|
||||
size_t m_poolUsed{};
|
||||
|
||||
public:
|
||||
StackFrame(WFCStackAllocator& allocator)
|
||||
: m_allocator(allocator)
|
||||
, m_savedAllocCount(allocator.m_allocations.size())
|
||||
, m_poolIndex(allocator.m_currentPoolIndex)
|
||||
, m_poolUsed(allocator.m_pools[m_poolIndex].used)
|
||||
{}
|
||||
|
||||
~StackFrame()
|
||||
{
|
||||
// Save the current used state of all pools
|
||||
m_savedUsed.reserve(allocator.m_pools.size());
|
||||
for (const auto& pool : allocator.m_pools) {
|
||||
m_savedUsed.push_back(pool.used);
|
||||
}
|
||||
}
|
||||
|
||||
~StackFrame() {
|
||||
// Restore the used state of all pools
|
||||
for (size_t i = 0; i < m_savedUsed.size() && i < m_allocator.m_pools.size(); ++i) {
|
||||
m_allocator.m_pools[i].used = m_savedUsed[i];
|
||||
for (size_t i = m_allocator.m_pools.size() - 1; i > m_poolIndex; --i)
|
||||
{
|
||||
m_allocator.m_pools[i].used = 0;
|
||||
}
|
||||
|
||||
// Remove any new pools that were added during this frame
|
||||
if (m_allocator.m_pools.size() > m_savedUsed.size()) {
|
||||
// Free the additional pools that were added
|
||||
for (size_t i = m_savedUsed.size(); i < m_allocator.m_pools.size(); ++i) {
|
||||
if (m_allocator.m_pools[i].ptr) {
|
||||
free_aligned_memory(m_allocator.m_pools[i].ptr);
|
||||
}
|
||||
}
|
||||
m_allocator.m_pools.resize(m_savedUsed.size());
|
||||
}
|
||||
|
||||
// Remove allocations that were made during this frame
|
||||
m_allocator.m_allocations.resize(m_savedAllocCount);
|
||||
m_allocator.m_pools[m_poolIndex].used = m_poolUsed;
|
||||
m_allocator.m_currentPoolIndex = m_poolIndex;
|
||||
}
|
||||
|
||||
// Non-copyable, movable
|
||||
@@ -236,106 +128,36 @@ public:
|
||||
/**
|
||||
* @brief Create a new stack frame for a branch
|
||||
*/
|
||||
StackFrame createFrame() {
|
||||
StackFrame createFrame()
|
||||
{
|
||||
return StackFrame(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get current memory usage
|
||||
*/
|
||||
size_t getUsed() const {
|
||||
size_t total = 0;
|
||||
for (const auto& pool : m_pools) {
|
||||
total += pool.used;
|
||||
}
|
||||
return total;
|
||||
constexpr size_t getCapacity() const
|
||||
{
|
||||
return m_pools[m_currentPoolIndex].size - m_pools[m_currentPoolIndex].used;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get total capacity
|
||||
*/
|
||||
size_t getCapacity() const {
|
||||
size_t total = 0;
|
||||
for (const auto& pool : m_pools) {
|
||||
total += pool.size;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get allocation count
|
||||
*/
|
||||
size_t getAllocationCount() const { return m_allocations.size(); }
|
||||
|
||||
/**
|
||||
* @brief Reset the allocator (useful for reusing between WFC runs)
|
||||
*/
|
||||
void reset() {
|
||||
for (auto& pool : m_pools) {
|
||||
pool.used = 0;
|
||||
}
|
||||
m_allocations.clear();
|
||||
static constexpr size_t alignUp(size_t value)
|
||||
{
|
||||
return (value + 8 - 1) & ~(8 - 1);
|
||||
}
|
||||
|
||||
private:
|
||||
void addPool(size_t size) {
|
||||
void* ptr = allocate_aligned_memory(64, size); // 64-byte alignment for cache efficiency
|
||||
constexpr void addPool(size_t size)
|
||||
{
|
||||
void* ptr = new char[size]; // Allocate bytes
|
||||
if (!ptr) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
m_pools.emplace_back(ptr, size);
|
||||
}
|
||||
|
||||
size_t alignUp(size_t value, size_t alignment) const {
|
||||
return (value + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<MemoryPool> m_pools;
|
||||
std::vector<Block> m_allocations;
|
||||
size_t m_currentPoolIndex{};
|
||||
};
|
||||
|
||||
#else // WFC_USE_STACK_ALLOCATOR not defined
|
||||
/**
|
||||
* @brief Simplified allocator using standard malloc/free
|
||||
*/
|
||||
class WFCStackAllocator {
|
||||
public:
|
||||
explicit WFCStackAllocator(size_t = 1024 * 1024) {}
|
||||
~WFCStackAllocator() = default;
|
||||
|
||||
// Non-copyable, non-movable for consistency
|
||||
WFCStackAllocator(const WFCStackAllocator&) = delete;
|
||||
WFCStackAllocator& operator=(const WFCStackAllocator&) = delete;
|
||||
WFCStackAllocator(WFCStackAllocator&&) = delete;
|
||||
WFCStackAllocator& operator=(WFCStackAllocator&&) = delete;
|
||||
|
||||
void* allocate(size_t size, size_t alignment = 8) {
|
||||
return allocate_aligned_memory(alignment, size);
|
||||
}
|
||||
|
||||
void deallocate(void* ptr) {
|
||||
free_aligned_memory(ptr);
|
||||
}
|
||||
|
||||
class StackFrame {
|
||||
public:
|
||||
StackFrame(WFCStackAllocator&) {}
|
||||
~StackFrame() = default;
|
||||
StackFrame(const StackFrame&) = delete;
|
||||
StackFrame& operator=(const StackFrame&) = delete;
|
||||
StackFrame(StackFrame&&) = default;
|
||||
StackFrame& operator=(StackFrame&&) = default;
|
||||
};
|
||||
|
||||
StackFrame createFrame() { return StackFrame(*this); }
|
||||
size_t getUsed() const { return 0; }
|
||||
size_t getCapacity() const { return 0; }
|
||||
size_t getAllocationCount() const { return 0; }
|
||||
void reset() {}
|
||||
};
|
||||
#endif // WFC_USE_STACK_ALLOCATOR
|
||||
|
||||
/**
|
||||
* @brief Custom allocator adapter for STL containers using WFCStackAllocator
|
||||
*/
|
||||
@@ -363,7 +185,11 @@ public:
|
||||
: m_allocator(other.m_allocator) {}
|
||||
|
||||
pointer allocate(size_type n) {
|
||||
return static_cast<pointer>(m_allocator->allocate(n * sizeof(T), alignof(T)));
|
||||
size_t size = n * sizeof(T);
|
||||
size_t alignment = alignof(T);
|
||||
// Ensure alignment
|
||||
size = (size + alignment - 1) & ~(alignment - 1);
|
||||
return static_cast<pointer>(m_allocator->allocate(size));
|
||||
}
|
||||
|
||||
void deallocate(pointer ptr, size_type) {
|
||||
|
||||
@@ -9,6 +9,7 @@ FetchContent_MakeAvailable(googletest)
|
||||
|
||||
set(TEST_SOURCES
|
||||
test_main.cpp
|
||||
test_allocator.cpp
|
||||
)
|
||||
|
||||
# Create test executable
|
||||
|
||||
316
tests/test_allocator.cpp
Normal file
316
tests/test_allocator.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <cstring>
|
||||
#include "nd-wfc/wfc_allocator.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
// Test fixture for WFCStackAllocator tests
|
||||
class WFCStackAllocatorTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Setup if needed
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
// Cleanup if needed
|
||||
}
|
||||
};
|
||||
|
||||
// Test basic allocation and deallocation
|
||||
TEST_F(WFCStackAllocatorTest, BasicAllocation) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
void* ptr1 = allocator.allocate(64);
|
||||
ASSERT_NE(ptr1, nullptr);
|
||||
|
||||
void* ptr2 = allocator.allocate(128);
|
||||
ASSERT_NE(ptr2, nullptr);
|
||||
ASSERT_NE(ptr1, ptr2); // Should be different addresses
|
||||
|
||||
// Check that allocations are properly aligned
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr1) % 8, 0);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr2) % 8, 0);
|
||||
|
||||
// deallocate doesn't do anything in this allocator
|
||||
allocator.deallocate(ptr1);
|
||||
allocator.deallocate(ptr2);
|
||||
}
|
||||
|
||||
// Test alignment
|
||||
TEST_F(WFCStackAllocatorTest, Alignment) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
// Test various sizes and ensure 8-byte alignment
|
||||
for (size_t size : {1, 3, 7, 9, 15, 17}) {
|
||||
void* ptr = allocator.allocate(size);
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % 8, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Test stack frame functionality
|
||||
TEST_F(WFCStackAllocatorTest, StackFrame) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
// Allocate some memory in the root frame
|
||||
void* rootPtr = allocator.allocate(64);
|
||||
ASSERT_NE(rootPtr, nullptr);
|
||||
|
||||
size_t initialCapacity = allocator.getCapacity();
|
||||
|
||||
{
|
||||
// Create a new stack frame
|
||||
auto frame = allocator.createFrame();
|
||||
|
||||
// Allocate in the new frame
|
||||
void* framePtr1 = allocator.allocate(32);
|
||||
void* framePtr2 = allocator.allocate(48);
|
||||
ASSERT_NE(framePtr1, nullptr);
|
||||
ASSERT_NE(framePtr2, nullptr);
|
||||
|
||||
// Capacity should be reduced
|
||||
EXPECT_LT(allocator.getCapacity(), initialCapacity);
|
||||
|
||||
// Frame goes out of scope, memory should be freed
|
||||
}
|
||||
|
||||
// After frame destruction, capacity should be restored
|
||||
EXPECT_EQ(allocator.getCapacity(), initialCapacity);
|
||||
|
||||
// We can still allocate (should reuse the freed space)
|
||||
void* newPtr = allocator.allocate(32);
|
||||
ASSERT_NE(newPtr, nullptr);
|
||||
}
|
||||
|
||||
// Test nested stack frames
|
||||
TEST_F(WFCStackAllocatorTest, NestedStackFrames) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
void* rootPtr = allocator.allocate(32);
|
||||
size_t rootCapacity = allocator.getCapacity();
|
||||
|
||||
{
|
||||
auto frame1 = allocator.createFrame();
|
||||
void* frame1Ptr = allocator.allocate(32);
|
||||
size_t frame1Capacity = allocator.getCapacity();
|
||||
|
||||
{
|
||||
auto frame2 = allocator.createFrame();
|
||||
void* frame2Ptr = allocator.allocate(32);
|
||||
size_t frame2Capacity = allocator.getCapacity();
|
||||
|
||||
// Each nested frame should have less capacity
|
||||
EXPECT_LT(frame2Capacity, frame1Capacity);
|
||||
EXPECT_LT(frame1Capacity, rootCapacity);
|
||||
|
||||
// frame2 goes out of scope
|
||||
}
|
||||
|
||||
// Back to frame1 capacity
|
||||
EXPECT_EQ(allocator.getCapacity(), frame1Capacity);
|
||||
|
||||
// frame1 goes out of scope
|
||||
}
|
||||
|
||||
// Back to root capacity
|
||||
EXPECT_EQ(allocator.getCapacity(), rootCapacity);
|
||||
}
|
||||
|
||||
// Test automatic pool expansion
|
||||
TEST_F(WFCStackAllocatorTest, PoolExpansion) {
|
||||
WFC::WFCStackAllocator allocator(128); // Small initial pool
|
||||
|
||||
// Allocate until we exceed the initial pool
|
||||
std::vector<void*> allocations;
|
||||
size_t totalAllocated = 0;
|
||||
|
||||
while (totalAllocated < 1000) { // More than initial capacity
|
||||
void* ptr = allocator.allocate(64);
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
allocations.push_back(ptr);
|
||||
totalAllocated += 64;
|
||||
}
|
||||
|
||||
// All allocations should be valid and aligned
|
||||
for (void* ptr : allocations) {
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % 8, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Test constructor with user-provided memory
|
||||
TEST_F(WFCStackAllocatorTest, UserProvidedMemory) {
|
||||
const size_t bufferSize = 512;
|
||||
std::vector<uint8_t> buffer(bufferSize);
|
||||
|
||||
std::span<uint8_t> span(buffer.data(), buffer.size());
|
||||
WFC::WFCStackAllocator allocator(span);
|
||||
|
||||
// Should be able to allocate from the provided buffer
|
||||
void* ptr1 = allocator.allocate(64);
|
||||
ASSERT_NE(ptr1, nullptr);
|
||||
|
||||
// Pointer should be within our buffer
|
||||
EXPECT_GE(ptr1, buffer.data());
|
||||
EXPECT_LT(static_cast<uint8_t*>(ptr1) + 64, buffer.data() + bufferSize);
|
||||
|
||||
void* ptr2 = allocator.allocate(128);
|
||||
ASSERT_NE(ptr2, nullptr);
|
||||
|
||||
// Should still be able to expand to new pools when user buffer is exhausted
|
||||
void* ptr3 = allocator.allocate(bufferSize); // Larger than user buffer
|
||||
ASSERT_NE(ptr3, nullptr);
|
||||
}
|
||||
|
||||
// Test allocator adapter
|
||||
TEST_F(WFCStackAllocatorTest, AllocatorAdapter) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
WFC::WFCStackAllocatorAdapter<int> adapter(allocator);
|
||||
|
||||
// Test allocation
|
||||
int* ptr = adapter.allocate(10); // Space for 10 ints
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
|
||||
// Should be properly aligned for int
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % alignof(int), 0);
|
||||
|
||||
// Test deallocation
|
||||
adapter.deallocate(ptr, 10);
|
||||
}
|
||||
|
||||
// Test STL container with custom allocator
|
||||
TEST_F(WFCStackAllocatorTest, STLContainerWithAdapter) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
using IntVector = std::vector<int, WFC::WFCStackAllocatorAdapter<int>>;
|
||||
|
||||
{
|
||||
auto frame = allocator.createFrame();
|
||||
IntVector vec((WFC::WFCStackAllocatorAdapter<int>(allocator)));
|
||||
|
||||
// Add some elements
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
vec.push_back(i);
|
||||
}
|
||||
|
||||
EXPECT_EQ(vec.size(), 10);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
EXPECT_EQ(vec[i], i);
|
||||
}
|
||||
|
||||
// Vector goes out of scope, memory freed with frame
|
||||
}
|
||||
|
||||
// Should be able to allocate again (reusing freed memory)
|
||||
void* newAlloc = allocator.allocate(64);
|
||||
ASSERT_NE(newAlloc, nullptr);
|
||||
}
|
||||
|
||||
// Test alignment helper function
|
||||
TEST_F(WFCStackAllocatorTest, AlignUp) {
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(0), 0);
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(1), 8);
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(7), 8);
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(8), 8);
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(9), 16);
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(15), 16);
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(16), 16);
|
||||
}
|
||||
|
||||
// Test edge case: allocate zero bytes
|
||||
TEST_F(WFCStackAllocatorTest, ZeroAllocation) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
void* ptr = allocator.allocate(0);
|
||||
// Should return a valid pointer (can be null or non-null depending on implementation)
|
||||
// But should not crash
|
||||
|
||||
allocator.deallocate(ptr);
|
||||
}
|
||||
|
||||
// Test edge case: very large allocation
|
||||
TEST_F(WFCStackAllocatorTest, LargeAllocation) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
// Allocate something larger than initial capacity
|
||||
void* ptr = allocator.allocate(2000);
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % 8, 0);
|
||||
}
|
||||
|
||||
// Test memory reuse after frame destruction
|
||||
TEST_F(WFCStackAllocatorTest, MemoryReuse) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
size_t initialCapacity = allocator.getCapacity();
|
||||
|
||||
{
|
||||
auto frame = allocator.createFrame();
|
||||
allocator.allocate(64);
|
||||
allocator.allocate(64);
|
||||
|
||||
// Should have less capacity during frame
|
||||
EXPECT_LT(allocator.getCapacity(), initialCapacity);
|
||||
}
|
||||
|
||||
// After frame destruction, should be back to initial capacity
|
||||
EXPECT_EQ(allocator.getCapacity(), initialCapacity);
|
||||
}
|
||||
|
||||
// Test multiple frames and complex nesting
|
||||
TEST_F(WFCStackAllocatorTest, ComplexFrameNesting) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
// Root allocations
|
||||
void* root1 = allocator.allocate(32);
|
||||
void* root2 = allocator.allocate(32);
|
||||
|
||||
{
|
||||
auto frame1 = allocator.createFrame();
|
||||
void* f1_1 = allocator.allocate(32);
|
||||
void* f1_2 = allocator.allocate(32);
|
||||
|
||||
{
|
||||
auto frame2 = allocator.createFrame();
|
||||
void* f2_1 = allocator.allocate(32);
|
||||
|
||||
{
|
||||
auto frame3 = allocator.createFrame();
|
||||
void* f3_1 = allocator.allocate(32);
|
||||
// frame3 ends
|
||||
}
|
||||
|
||||
void* f2_2 = allocator.allocate(32);
|
||||
// frame2 ends
|
||||
}
|
||||
|
||||
void* f1_3 = allocator.allocate(32);
|
||||
// frame1 ends
|
||||
}
|
||||
|
||||
// All frame memory should be freed, root allocations still valid
|
||||
void* root3 = allocator.allocate(32); // Should reuse freed space
|
||||
ASSERT_NE(root3, nullptr);
|
||||
}
|
||||
|
||||
// Test that deallocation is a no-op
|
||||
TEST_F(WFCStackAllocatorTest, DeallocateIsNoOp) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
void* ptr = allocator.allocate(64);
|
||||
size_t capacityBefore = allocator.getCapacity();
|
||||
|
||||
// deallocate should do nothing
|
||||
allocator.deallocate(ptr);
|
||||
|
||||
// Capacity should be unchanged
|
||||
EXPECT_EQ(allocator.getCapacity(), capacityBefore);
|
||||
|
||||
// Should be able to allocate again
|
||||
void* ptr2 = allocator.allocate(64);
|
||||
ASSERT_NE(ptr2, nullptr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -2,8 +2,11 @@
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include "nd-wfc/wfc.hpp"
|
||||
#include "nd-wfc/worlds.hpp"
|
||||
#include "nd-wfc/wfc_allocator.hpp"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
Reference in New Issue
Block a user