summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/memory.cpp206
-rw-r--r--src/window.cpp34
-rw-r--r--test/CMakeLists.txt9
-rw-r--r--test/memory/CMakeLists.txt16
-rw-r--r--test/memory/general.cpp220
-rw-r--r--test/memory/main.cpp6
7 files changed, 437 insertions, 56 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c921388..c938efe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -178,3 +178,5 @@ target_include_directories(pke PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(pke PUBLIC imguidocked)
add_dependencies(pke shaders)
+
+add_subdirectory(test)
diff --git a/src/memory.cpp b/src/memory.cpp
index 538ffd8..853c6fe 100644
--- a/src/memory.cpp
+++ b/src/memory.cpp
@@ -35,6 +35,12 @@ int64_t InitNewBucket(int64_t sz, bool transient = false) {
bkt.maxBlockCount = blockCount < 10 ? 10 : blockCount;
bkt.blocks = reinterpret_cast<char *>(std::malloc(sz));
bkt.ptr = bkt.blocks + (sizeof(MemBlock) * bkt.maxBlockCount);
+ size_t misalignment = reinterpret_cast<uint64_t>(bkt.ptr) % MAXIMUM_ALIGNMENT;
+ if (misalignment != 0) {
+ size_t moreBlocks = misalignment / sizeof(MemBlock);
+ bkt.maxBlockCount += moreBlocks;
+ bkt.ptr += (MAXIMUM_ALIGNMENT - misalignment);
+ }
bkt.transient = transient;
auto *memBlock = reinterpret_cast<MemBlock *>(bkt.blocks);
memBlock->data = bkt.ptr;
@@ -58,43 +64,86 @@ void DestroyBucket(MemBucket *bkt) {
void *Pke_New(std::size_t sz, std::size_t alignment, MemBucket *bkt) {
MemBlock *blocks = reinterpret_cast<MemBlock *>(bkt->blocks);
std::size_t calculatedAlignment = alignment < MINIMUM_ALIGNMENT ? MINIMUM_ALIGNMENT : alignment;
- int64_t availBlockIndex = -1;
+ std::size_t misalignment = 0;
+ long index = -1;
+ MemBlock *block = nullptr;
void *data = nullptr;
for (int64_t i = 0; i <= bkt->lastEmptyBlockIndex; ++i) {
- auto blk = blocks[i];
- std::size_t misalignment = reinterpret_cast<std::size_t>(blk.data) % calculatedAlignment;
- if (misalignment != 0)
- continue;
- if (blk.size > sz) {
- availBlockIndex = i;
+ auto &blk = blocks[i];
+ misalignment = reinterpret_cast<std::size_t>(blk.data) % calculatedAlignment;
+ misalignment = (calculatedAlignment - misalignment) % calculatedAlignment;
+ if (blk.size >= sz + misalignment) {
+ index = i;
+ block = &blk;
break;
}
}
- if (availBlockIndex != -1) {
- data = blocks[availBlockIndex].data;
- blocks[availBlockIndex].data += sz;
- blocks[availBlockIndex].size -= sz;
- assert(blocks[availBlockIndex].size < bkt->size);
- if (blocks[availBlockIndex].size == 0) {
- if (availBlockIndex != bkt->lastEmptyBlockIndex) {
- blocks[availBlockIndex].data = blocks[bkt->lastEmptyBlockIndex].data;
- blocks[availBlockIndex].size = blocks[bkt->lastEmptyBlockIndex].size;
+ if (block != nullptr) {
+ MemBlock *nextBlock = &blocks[index + 1];
+ bool touchesValidNeighbor = index != bkt->lastEmptyBlockIndex && nextBlock->data == block->data + block->size;
+ data = block->data + misalignment;
+ if (misalignment) {
+ size_t afterSize = block->size - misalignment - sz;
+ block->size = misalignment;
+ if (afterSize) {
+ if (index == bkt->maxBlockCount) {
+ bkt->lostBytes += afterSize;
+ } else if (touchesValidNeighbor) {
+ nextBlock->data -= afterSize;
+ nextBlock->size += afterSize;
+ } else {
+ if (index != bkt->lastEmptyBlockIndex) {
+ long moveCount = bkt->lastEmptyBlockIndex - index;
+ if (moveCount > 0) {
+ char *beforePos = bkt->blocks + (sizeof(MemBlock) * (index + 1));
+ char *afterPos = bkt->blocks + (sizeof(MemBlock) * (index + 2));
+ memmove(afterPos, beforePos, sizeof(MemBlock) * moveCount);
+ }
+ }
+ bkt->lastEmptyBlockIndex += 1;
+ nextBlock->data = block->data + misalignment + sz;
+ nextBlock->size = afterSize;
+ }
+ }
+ } else {
+ size_t afterSize = block->size - sz;
+ if (afterSize && !touchesValidNeighbor) {
+ block->data += sz;
+ block->size -= sz;
+ } else {
+ if (afterSize && touchesValidNeighbor) {
+ nextBlock->data -= afterSize;
+ nextBlock->size += afterSize;
+ }
+ long moveCount = bkt->lastEmptyBlockIndex - index;
+ if (moveCount > 0) {
+ char *beforePos = bkt->blocks + (sizeof(MemBlock) * (index + 1));
+ char *afterPos = bkt->blocks + (sizeof(MemBlock) * (index + 0));
+ memmove(afterPos, beforePos, sizeof(MemBlock) * moveCount);
+ bkt->lastEmptyBlockIndex -= 1;
+ }
}
- bkt->lastEmptyBlockIndex -= 1;
}
} else {
assert(bkt->head + sz <= bkt->size && "memory bucket specified, but full");
- std::size_t misalignment = reinterpret_cast<std::size_t>(bkt->ptr + bkt->head) % calculatedAlignment;
- std::size_t padding = calculatedAlignment - misalignment;
+ misalignment = reinterpret_cast<std::size_t>(bkt->ptr + bkt->head) % calculatedAlignment;
+ misalignment = (calculatedAlignment - misalignment) % calculatedAlignment;
if (misalignment != 0) {
- bkt->lastEmptyBlockIndex += 1;
- blocks[bkt->lastEmptyBlockIndex].data = bkt->ptr + bkt->head;
- blocks[bkt->lastEmptyBlockIndex].size = padding;
+ if (bkt->lastEmptyBlockIndex == bkt->maxBlockCount) {
+ bkt->lostBytes += misalignment;
+ } else {
+ bkt->lastEmptyBlockIndex += 1;
+ blocks[bkt->lastEmptyBlockIndex].data = bkt->ptr + bkt->head;
+ blocks[bkt->lastEmptyBlockIndex].size = misalignment;
+ }
+ bkt->head = bkt->head + misalignment;
}
- data = bkt->ptr + bkt->head + padding;
- bkt->head += sz + padding;
+ data = bkt->ptr + bkt->head;
+ bkt->head = bkt->head + sz;
}
bkt->allocs++;
+ assert(data >= bkt->ptr && "allocated data is before bucket data");
+ assert(data <= bkt->ptr + bkt->size && "allocated data is after bucket data");
return data;
}
@@ -111,6 +160,52 @@ void *Pke_New(std::size_t sz, std::size_t alignment) {
return Pke_New(sz, alignment, bkt);
}
+void inline Pke_CollapseEmptyBlocksToHead(MemBucket *bkt) {
+ MemBlock *blocks = reinterpret_cast<MemBlock *>(bkt->blocks);
+ while (bkt->lastEmptyBlockIndex > -1) {
+ MemBlock *lastBlock = &blocks[bkt->lastEmptyBlockIndex];
+ if (lastBlock->data + lastBlock->size != bkt->ptr + bkt->head) {
+ return;
+ }
+ bkt->head -= lastBlock->size;
+ lastBlock->data = 0;
+ lastBlock->size = 0;
+ bkt->lastEmptyBlockIndex -= 1;
+ }
+}
+
+void inline Pke_CollapseBlocks(MemBucket *bkt) {
+ MemBlock *blocks = reinterpret_cast<MemBlock *>(bkt->blocks);
+ long skipDistance = 1;
+ long lastStartingIndex = 0;
+ for (long i = 0; i <= bkt->lastEmptyBlockIndex - 1; ++i) {
+ lastStartingIndex = i;
+ MemBlock &block = blocks[i];
+ MemBlock &nextBlock = blocks[i + skipDistance];
+ if (block.data + block.size == nextBlock.data) {
+ block.size += nextBlock.size;
+ nextBlock.size = 0;
+ skipDistance += 1;
+ i -= 1;
+ } else {
+ if (skipDistance > 1) {
+ char *beforePos = bkt->blocks + (sizeof(MemBlock) * (i + skipDistance));
+ char *afterPos = bkt->blocks + (sizeof(MemBlock) * (i + 1));
+ memmove(afterPos, beforePos, sizeof(MemBlock) * (skipDistance - 1));
+ bkt->lastEmptyBlockIndex -= (skipDistance - 1);
+ }
+ i += skipDistance - 1;
+ skipDistance = 1;
+ }
+ }
+ if (skipDistance > 1) {
+ char *beforePos = bkt->blocks + (sizeof(MemBlock) * (lastStartingIndex + skipDistance));
+ char *afterPos = bkt->blocks + (sizeof(MemBlock) * (lastStartingIndex + 1));
+ memmove(afterPos, beforePos, sizeof(MemBlock) * (skipDistance - 1));
+ bkt->lastEmptyBlockIndex -= (skipDistance - 1);
+ }
+}
+
void Pke_Delete(const void *ptr, std::size_t sz, MemBucket *bkt) {
assert(ptr >= bkt->ptr && ptr < bkt->ptr + bkt->size && "pointer not in memory bucket range");
bkt->allocs--;
@@ -121,28 +216,53 @@ void Pke_Delete(const void *ptr, std::size_t sz, MemBucket *bkt) {
}
if (ptr == bkt->ptr + bkt->head - sz) {
bkt->head -= sz;
- } else {
- MemBlock *blocks = reinterpret_cast<MemBlock *>(bkt->blocks);
- for (int64_t i = 0; i < bkt->lastEmptyBlockIndex; ++i) {
- auto &blk = blocks[i];
- if (blk.data == reinterpret_cast<const char *>(ptr) + sz) {
- blk.data -= sz;
- blk.size += sz;
- return;
- }
- if (reinterpret_cast<const char *>(ptr) == blk.data + blk.size ) {
- blk.size += sz;
- return;
- }
+ Pke_CollapseEmptyBlocksToHead(bkt);
+ return;
+ }
+ MemBlock *blocks = reinterpret_cast<MemBlock *>(bkt->blocks);
+ size_t prevBlockIndex = 0xFFFFFFFFFFFFFFFF;
+ void *prevPointer = reinterpret_cast<void *>(prevBlockIndex);
+ bool found = false;
+ for (int64_t i = 0; i <= bkt->lastEmptyBlockIndex; ++i) {
+ auto &blk = blocks[i];
+ if (blk.data < ptr && prevPointer < blk.data) {
+ prevBlockIndex = i;
+ prevPointer = blk.data;
}
- if (bkt->lastEmptyBlockIndex != bkt->maxBlockCount) {
- bkt->lastEmptyBlockIndex += 1;
- blocks[bkt->lastEmptyBlockIndex].data = reinterpret_cast<char *>(const_cast<void *>(ptr));
- blocks[bkt->lastEmptyBlockIndex].size = sz;
- } else {
+ if (blk.data == reinterpret_cast<const char *>(ptr) + sz) {
+ blk.data -= sz;
+ blk.size += sz;
+ found = true;
+ break;
+ }
+ if (reinterpret_cast<const char *>(ptr) == blk.data + blk.size ) {
+ blk.size += sz;
+ found = true;
+ break;
+ }
+ }
+ if (found == false) {
+ if (bkt->lastEmptyBlockIndex == bkt->maxBlockCount) {
bkt->lostBytes += sz;
+ } else {
+ MemBlock *targetBlock = nullptr;
+ if (prevBlockIndex < bkt->lastEmptyBlockIndex) {
+ long moveCount = bkt->lastEmptyBlockIndex - prevBlockIndex;
+ assert(moveCount > 0);
+ char *beforePos = bkt->blocks + (sizeof(MemBlock) * (prevBlockIndex + 1));
+ char *afterPos = bkt->blocks + (sizeof(MemBlock) * (prevBlockIndex + 2));
+ memmove(afterPos, beforePos, sizeof(MemBlock) * moveCount);
+ targetBlock = &blocks[prevBlockIndex + 1];
+ } else {
+ targetBlock = &blocks[bkt->lastEmptyBlockIndex + 1];
+ }
+ bkt->lastEmptyBlockIndex += 1;
+ targetBlock->data = reinterpret_cast<char *>(const_cast<void *>(ptr));
+ targetBlock->size = sz;
}
}
+ Pke_CollapseBlocks(bkt);
+ Pke_CollapseEmptyBlocksToHead(bkt);
}
void Pke_Delete(const void *ptr, std::size_t sz) {
@@ -207,7 +327,7 @@ uint64_t Buckets_NewHandle(std::size_t bucketBytes, std::size_t alignment, uint6
}
void Pke_DebugPrint() {
- printf("Memory Manager printout:\nBucket count: %li\n", bucketHead + 1);
+ printf("Memory Manager printout:\nBucket count: %li\n", bucketHead);
for (long i = 0; i < bucketHead; ++i) {
printf("- bucket #%li\n", i);
printf("\tsize: %li\n", buckets[i].size);
diff --git a/src/window.cpp b/src/window.cpp
index 480175a..3773643 100644
--- a/src/window.cpp
+++ b/src/window.cpp
@@ -242,6 +242,7 @@ unsigned int FindQueueFamilyIndex(VkPhysicalDevice device, short hasPresentSuppo
void PKE_vkFreeFunction(void *pUserData, void *pMemory) {
pke_vkAllocData *chunk = nullptr;
+ if (pMemory == nullptr) return;
size_t index = -1;
size_t count = vulkanAllocs->Count();
for (long i = 0; i < count; ++i) {
@@ -257,11 +258,13 @@ void PKE_vkFreeFunction(void *pUserData, void *pMemory) {
}
void *PKE_vkAllocateFunction(void *pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocScope) {
- size_t alignedSize = size + ((size % alignment) == 0 ? 0 : (alignment - (size % alignment)));
- void *ptr = Pke_New(alignedSize, alignment, MemBkt_Vulkan);
+ if (size == 0) {
+ return nullptr;
+ }
+ void *ptr = Pke_New(size, alignment, MemBkt_Vulkan);
vulkanAllocs->Push({
.data = ptr,
- .size = alignedSize,
+ .size = size,
});
return ptr;
}
@@ -269,22 +272,27 @@ void *PKE_vkAllocateFunction(void *pUserData, size_t size, size_t alignment, VkS
void *PKE_vkReallocationFunction(void *pUserData, void *pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocScope) {
pke_vkAllocData *chunk = nullptr;
size_t index = -1;
- if (pOriginal) {
- size_t count = vulkanAllocs->Count();
- for (long i = 0; i < count; ++i) {
- if ((*vulkanAllocs)[i].data == pOriginal) {
- chunk = &(*vulkanAllocs)[i];
- index = i;
- break;
- }
+ if (pOriginal == nullptr) {
+ return PKE_vkAllocateFunction(pUserData, size, alignment, allocScope);
+ }
+ if (size == 0) {
+ PKE_vkFreeFunction(pUserData, pOriginal);
+ return nullptr;
+ }
+ size_t count = vulkanAllocs->Count();
+ for (long i = 0; i < count; ++i) {
+ if ((*vulkanAllocs)[i].data == pOriginal) {
+ chunk = &(*vulkanAllocs)[i];
+ index = i;
+ break;
}
}
assert(chunk != nullptr);
- if (chunk->size > size) {
+ if (chunk->size >= size) {
return pOriginal;
}
void *newPtr = PKE_vkAllocateFunction(pUserData, size, alignment, allocScope);
- memcpy(newPtr, chunk->data, chunk->size);
+ memcpy(newPtr, chunk->data, (chunk->size < size ? chunk->size : size) - 1);
Pke_Delete(chunk->data, chunk->size, MemBkt_Vulkan);
vulkanAllocs->Remove(index);
return newPtr;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..760756d
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,9 @@
+include(FetchContent)
+FetchContent_Declare(
+ googletest
+ URL https://github.com/google/googletest/archive/8d51dc50eb7e7698427fed81b85edad0e032112e.zip
+)
+FetchContent_MakeAvailable(googletest)
+
+add_subdirectory(memory)
+
diff --git a/test/memory/CMakeLists.txt b/test/memory/CMakeLists.txt
new file mode 100644
index 0000000..ede5be4
--- /dev/null
+++ b/test/memory/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(BINARY pke_test_memory)
+
+set(TEST_SOURCE_FILES
+ main.cpp
+ general.cpp
+ ../../src/memory.cpp
+)
+
+add_executable(${BINARY} ${TEST_SOURCE_FILES})
+
+add_test(NAME ${BINARY} COMMAND ${BINARY})
+
+target_include_directories(${BINARY} PUBLIC "../../src")
+
+target_link_libraries(${BINARY} PUBLIC gmock)
+
diff --git a/test/memory/general.cpp b/test/memory/general.cpp
new file mode 100644
index 0000000..825e2f6
--- /dev/null
+++ b/test/memory/general.cpp
@@ -0,0 +1,220 @@
+
+#include "memory.hpp"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+struct TMemBlock {
+ char *data;
+ size_t size;
+};
+
+struct TMemBucket {
+ int64_t size;
+ int64_t head;
+ int64_t lostBytes;
+ int64_t allocs;
+ int64_t lastEmptyBlockIndex;
+ int64_t maxBlockCount;
+ TMemBlock *blocks;
+ char *ptr;
+ bool transient;
+};
+
+class MemoryTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ MemBkt_Test = Pke_BeginTransientBucket(1024 * 1024);
+ bkt = reinterpret_cast<TMemBucket *>(MemBkt_Test);
+ };
+ void TearDown() override {
+ Pke_EndTransientBucket(MemBkt_Test);
+ };
+ MemBucket *MemBkt_Test = nullptr;
+ TMemBucket *bkt = nullptr;
+};
+
+/*
+ * Test that memory can be allocated predictably
+ */
+TEST_F(MemoryTest, EnsureAllocation001) {
+ // arrange
+ size_t sz = 2;
+ size_t alignment = 1;
+
+ // act
+ Pke_New(sz, alignment, MemBkt_Test);
+
+ // assert
+ EXPECT_EQ(-1, bkt->lastEmptyBlockIndex);
+ EXPECT_EQ(sz, bkt->head);
+};
+
+/*
+ * Test that memory can be freed predictably
+ */
+TEST_F(MemoryTest, EnsureFree001) {
+ // arrange
+ size_t sz = 2;
+ size_t alignment = 1;
+
+ // act
+ void *ptr1 = Pke_New(sz, alignment, MemBkt_Test);
+ Pke_Delete(ptr1, sz, MemBkt_Test);
+
+ // assert
+ EXPECT_EQ(-1, bkt->lastEmptyBlockIndex);
+ EXPECT_EQ(0, bkt->head);
+};
+
+
+/*
+ * Test that memory can be allocated and freed predictably
+ */
+class MemoryTestScenario100 : public MemoryTest {
+ void SetUp() override {
+ MemoryTest::SetUp();
+ for (long i = 0; i < cnt; ++i) {
+ ptrs[i] = Pke_New(sz[i], alignment[i], MemBkt_Test);
+ }
+ }
+protected:
+ void TestExpectedState() {
+ EXPECT_EQ(2, bkt->lastEmptyBlockIndex);
+ EXPECT_EQ(80, bkt->head);
+ EXPECT_EQ(bkt->ptr + 00, ptrs[0]);
+ EXPECT_EQ(bkt->ptr + 64, ptrs[1]);
+ EXPECT_EQ(bkt->ptr + 16, ptrs[2]);
+ EXPECT_EQ(bkt->ptr + 8, ptrs[3]);
+ }
+ const long cnt = 4;
+ const size_t sz[4] = {2, 16, 16, 4};
+ const size_t alignment[4] = {1, 64, 16, 8};
+ void * ptrs[4];
+};
+
+/* allocation events
+ * 1st = alignment 01, size 02, move head ( =02 )
+ * 2nd = alignment 64, size 16, move head ( =80 ) memBlock { +02, 62 }
+ * 3rd = alignment 16, size 16, memBlock { +2, 14 } memBlock { +32, 32 }
+ * 4th = alignment 08, size 04, memBlock { +2, 06 } memBlock { +12, 04 } memBlock { +32, 32 }
+ */
+
+TEST_F(MemoryTestScenario100, EnsureAllocFree0000) {
+ EXPECT_NO_FATAL_FAILURE(this->TestExpectedState());
+
+ // arrange
+
+ // act
+
+ // assert
+ EXPECT_EQ(bkt->ptr + 2, bkt->blocks[0].data);
+ EXPECT_EQ(6, bkt->blocks[0].size);
+
+ EXPECT_EQ(bkt->ptr + 12, bkt->blocks[1].data);
+ EXPECT_EQ(4, bkt->blocks[1].size);
+
+ EXPECT_EQ(bkt->ptr + 32, bkt->blocks[2].data);
+ EXPECT_EQ(32, bkt->blocks[2].size);
+}
+
+TEST_F(MemoryTestScenario100, EnsureAllocFree1000) {
+ EXPECT_NO_FATAL_FAILURE(this->TestExpectedState());
+
+ // arrange
+ size_t expectedHead = 80;
+ long freeIndex = 0;
+
+ // act
+ /* free events
+ * 1st = ptr +00, size 02, memBlock { +00, 08 }
+ */
+ Pke_Delete(ptrs[freeIndex], sz[freeIndex], MemBkt_Test);
+
+ // assert
+ EXPECT_EQ(2, bkt->lastEmptyBlockIndex);
+ EXPECT_EQ(expectedHead, bkt->head);
+
+ EXPECT_EQ(bkt->ptr + 0, bkt->blocks[0].data);
+ EXPECT_EQ(8, bkt->blocks[0].size);
+
+ EXPECT_EQ(bkt->ptr + 12, bkt->blocks[1].data);
+ EXPECT_EQ(4, bkt->blocks[1].size);
+
+ EXPECT_EQ(bkt->ptr + 32, bkt->blocks[2].data);
+ EXPECT_EQ(32, bkt->blocks[2].size);
+};
+
+TEST_F(MemoryTestScenario100, EnsureAllocFree0100) {
+ EXPECT_NO_FATAL_FAILURE(this->TestExpectedState());
+
+ // arrange
+ size_t expectedHead = 32;
+ long freeIndex = 1;
+
+ // act
+ /* free events
+ * 1st = ptr +64, size 16, move head ( =32 ) memBlock { +02, 06 } memBlock { +12, 4 }
+ */
+ Pke_Delete(ptrs[freeIndex], sz[freeIndex], MemBkt_Test);
+
+ // assert
+ EXPECT_EQ(1, bkt->lastEmptyBlockIndex);
+ EXPECT_EQ(expectedHead, bkt->head);
+
+ EXPECT_EQ(bkt->ptr + 2, bkt->blocks[0].data);
+ EXPECT_EQ(6, bkt->blocks[0].size);
+
+ EXPECT_EQ(bkt->ptr + 12, bkt->blocks[1].data);
+ EXPECT_EQ(4, bkt->blocks[1].size);
+};
+
+TEST_F(MemoryTestScenario100, EnsureAllocFree0010) {
+ EXPECT_NO_FATAL_FAILURE(this->TestExpectedState());
+
+ // arrange
+ size_t expectedHead = 80;
+ long freeIndex = 2;
+
+ // act
+ /* free events
+ * 1st = ptr +16, size 16, memBlock { +02, 06 } memBlock { +12, 04 } memBlock { +16, 48 }
+ * memBlock { +02, 06 } memBlock { +12, 52 }
+ */
+ Pke_Delete(ptrs[freeIndex], sz[freeIndex], MemBkt_Test);
+
+ // assert
+ EXPECT_EQ(1, bkt->lastEmptyBlockIndex);
+ EXPECT_EQ(expectedHead, bkt->head);
+
+ EXPECT_EQ(bkt->ptr + 2, bkt->blocks[0].data);
+ EXPECT_EQ(6, bkt->blocks[0].size);
+
+ EXPECT_EQ(bkt->ptr + 12, bkt->blocks[1].data);
+ EXPECT_EQ(52, bkt->blocks[1].size);
+}
+
+TEST_F(MemoryTestScenario100, EnsureAllocFree0001) {
+ EXPECT_NO_FATAL_FAILURE(this->TestExpectedState());
+
+ // arrange
+ size_t expectedHead = 80;
+ long freeIndex = 3;
+
+ // act
+ /* free events
+ * 1st = ptr +08, size 4, memBlock { +02, 06 } memBlock { +08, 08 } memBlock { +32, 32 }
+ * memBlock { +02, 14 } memBlock { +32, 32 }
+ */
+ Pke_Delete(ptrs[freeIndex], sz[freeIndex], MemBkt_Test);
+
+ // assert
+ EXPECT_EQ(1, bkt->lastEmptyBlockIndex);
+ EXPECT_EQ(expectedHead, bkt->head);
+
+ EXPECT_EQ(bkt->ptr + 2, bkt->blocks[0].data);
+ EXPECT_EQ(14, bkt->blocks[0].size);
+
+ EXPECT_EQ(bkt->ptr + 32, bkt->blocks[1].data);
+ EXPECT_EQ(32, bkt->blocks[1].size);
+}
diff --git a/test/memory/main.cpp b/test/memory/main.cpp
new file mode 100644
index 0000000..4483c91
--- /dev/null
+++ b/test/memory/main.cpp
@@ -0,0 +1,6 @@
+#include "gtest/gtest.h"
+
+int main(int argc, char **argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}