aboutsummaryrefslogtreecommitdiff
path: root/pw_allocator/block_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'pw_allocator/block_test.cc')
-rw-r--r--pw_allocator/block_test.cc1195
1 files changed, 973 insertions, 222 deletions
diff --git a/pw_allocator/block_test.cc b/pw_allocator/block_test.cc
index 06772bd99..fd2906d3f 100644
--- a/pw_allocator/block_test.cc
+++ b/pw_allocator/block_test.cc
@@ -14,6 +14,7 @@
#include "pw_allocator/block.h"
+#include <cstdint>
#include <cstring>
#include "gtest/gtest.h"
@@ -23,101 +24,143 @@ using std::byte;
namespace pw::allocator {
-TEST(Block, CanCreateSingleBlock) {
+const size_t kCapacity = 0x20000;
+
+template <typename BlockType>
+void CanCreateSingleBlock() {
constexpr size_t kN = 200;
- alignas(Block*) byte bytes[kN];
+ alignas(BlockType*) byte bytes[kN];
- Block* block = nullptr;
- auto status = Block::Init(span(bytes, kN), &block);
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
- ASSERT_EQ(status, OkStatus());
EXPECT_EQ(block->OuterSize(), kN);
- EXPECT_EQ(block->InnerSize(),
- kN - sizeof(Block) - 2 * PW_ALLOCATOR_POISON_OFFSET);
+ EXPECT_EQ(block->InnerSize(), kN - BlockType::kBlockOverhead);
EXPECT_EQ(block->Prev(), nullptr);
- EXPECT_EQ(block->Next(), (Block*)((uintptr_t)block + kN));
- EXPECT_EQ(block->Used(), false);
- EXPECT_EQ(block->Last(), true);
+ EXPECT_EQ(block->Next(), nullptr);
+ EXPECT_FALSE(block->Used());
+ EXPECT_TRUE(block->Last());
+}
+TEST(GenericBlockTest, CanCreateSingleBlock) {
+ CanCreateSingleBlock<Block<>>();
+}
+TEST(CustomBlockTest, CanCreateSingleBlock) {
+ CanCreateSingleBlock<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CannotCreateUnalignedSingleBlock) {
+template <typename BlockType>
+void CannotCreateUnalignedSingleBlock() {
constexpr size_t kN = 1024;
// Force alignment, so we can un-force it below
- alignas(Block*) byte bytes[kN];
+ alignas(BlockType*) byte bytes[kN];
byte* byte_ptr = bytes;
- Block* block = nullptr;
- auto status = Block::Init(span(byte_ptr + 1, kN - 1), &block);
-
- EXPECT_EQ(status, Status::InvalidArgument());
+ Result<BlockType*> result = BlockType::Init(span(byte_ptr + 1, kN - 1));
+ EXPECT_FALSE(result.ok());
+ EXPECT_EQ(result.status(), Status::InvalidArgument());
+}
+TEST(GenericBlockTest, CannotCreateUnalignedSingleBlock) {
+ CannotCreateUnalignedSingleBlock<Block<>>();
+}
+TEST(CustomBlockTest, CannotCreateUnalignedSingleBlock) {
+ CannotCreateUnalignedSingleBlock<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CannotCreateTooSmallBlock) {
+template <typename BlockType>
+void CannotCreateTooSmallBlock() {
constexpr size_t kN = 2;
- alignas(Block*) byte bytes[kN];
- Block* block = nullptr;
- auto status = Block::Init(span(bytes, kN), &block);
+ alignas(BlockType*) byte bytes[kN];
- EXPECT_EQ(status, Status::InvalidArgument());
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ EXPECT_FALSE(result.ok());
+ EXPECT_EQ(result.status(), Status::ResourceExhausted());
+}
+TEST(GenericBlockTest, CannotCreateTooSmallBlock) {
+ CannotCreateTooSmallBlock<Block<>>();
+}
+TEST(CustomBlockTest, CannotCreateTooSmallBlock) {
+ CannotCreateTooSmallBlock<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CanSplitBlock) {
+TEST(CustomBlockTest, CannotCreateTooLargeBlock) {
+ using BlockType = Block<uint16_t, 512>;
+ constexpr size_t kN = 1024;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ EXPECT_FALSE(result.ok());
+ EXPECT_EQ(result.status(), Status::OutOfRange());
+}
+
+template <typename BlockType>
+void CanSplitBlock() {
constexpr size_t kN = 1024;
constexpr size_t kSplitN = 512;
- alignas(Block*) byte bytes[kN];
+ alignas(BlockType*) byte bytes[kN];
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
- Block* next_block = nullptr;
- auto status = block->Split(kSplitN, &next_block);
+ result = BlockType::Split(block1, kSplitN);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
- ASSERT_EQ(status, OkStatus());
- EXPECT_EQ(block->InnerSize(), kSplitN);
- EXPECT_EQ(block->OuterSize(),
- kSplitN + sizeof(Block) + 2 * PW_ALLOCATOR_POISON_OFFSET);
- EXPECT_EQ(block->Last(), false);
+ EXPECT_EQ(block1->InnerSize(), kSplitN);
+ EXPECT_EQ(block1->OuterSize(), kSplitN + BlockType::kBlockOverhead);
+ EXPECT_FALSE(block1->Last());
- EXPECT_EQ(next_block->OuterSize(),
- kN - kSplitN - sizeof(Block) - 2 * PW_ALLOCATOR_POISON_OFFSET);
- EXPECT_EQ(next_block->Used(), false);
- EXPECT_EQ(next_block->Last(), true);
+ EXPECT_EQ(block2->OuterSize(), kN - kSplitN - BlockType::kBlockOverhead);
+ EXPECT_FALSE(block2->Used());
+ EXPECT_TRUE(block2->Last());
- EXPECT_EQ(block->Next(), next_block);
- EXPECT_EQ(next_block->Prev(), block);
+ EXPECT_EQ(block1->Next(), block2);
+ EXPECT_EQ(block2->Prev(), block1);
+}
+TEST(GenericBlockTest, CanSplitBlock) { CanSplitBlock<Block<>>(); }
+TEST(CustomBlockTest, CanSplitBlock) {
+ CanSplitBlock<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CanSplitBlockUnaligned) {
+template <typename BlockType>
+void CanSplitBlockUnaligned() {
constexpr size_t kN = 1024;
constexpr size_t kSplitN = 513;
- alignas(Block*) byte bytes[kN];
+ alignas(BlockType*) byte bytes[kN];
- // We should split at sizeof(Block) + kSplitN bytes. Then
- // we need to round that up to an alignof(Block*) boundary.
+ // We should split at sizeof(BlockType) + kSplitN bytes. Then
+ // we need to round that up to an alignof(BlockType*) boundary.
uintptr_t split_addr = ((uintptr_t)&bytes) + kSplitN;
- split_addr += alignof(Block*) - (split_addr % alignof(Block*));
+ split_addr += alignof(BlockType*) - (split_addr % alignof(BlockType*));
uintptr_t split_len = split_addr - (uintptr_t)&bytes;
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
+
+ result = BlockType::Split(block1, kSplitN);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
+
+ EXPECT_EQ(block1->InnerSize(), split_len);
+ EXPECT_EQ(block1->OuterSize(), split_len + BlockType::kBlockOverhead);
- Block* next_block = nullptr;
- auto status = block->Split(kSplitN, &next_block);
+ EXPECT_EQ(block2->OuterSize(), kN - block1->OuterSize());
+ EXPECT_FALSE(block2->Used());
- ASSERT_EQ(status, OkStatus());
- EXPECT_EQ(block->InnerSize(), split_len);
- EXPECT_EQ(block->OuterSize(),
- split_len + sizeof(Block) + 2 * PW_ALLOCATOR_POISON_OFFSET);
- EXPECT_EQ(next_block->OuterSize(),
- kN - split_len - sizeof(Block) - 2 * PW_ALLOCATOR_POISON_OFFSET);
- EXPECT_EQ(next_block->Used(), false);
- EXPECT_EQ(block->Next(), next_block);
- EXPECT_EQ(next_block->Prev(), block);
+ EXPECT_EQ(block1->Next(), block2);
+ EXPECT_EQ(block2->Prev(), block1);
+}
+TEST(GenericBlockTest, CanSplitBlockUnaligned) { CanSplitBlock<Block<>>(); }
+TEST(CustomBlockTest, CanSplitBlockUnaligned) {
+ CanSplitBlock<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CanSplitMidBlock) {
+template <typename BlockType>
+void CanSplitMidBlock() {
// Split once, then split the original block again to ensure that the
// pointers get rewired properly.
// I.e.
@@ -130,282 +173,990 @@ TEST(Block, CanSplitMidBlock) {
constexpr size_t kN = 1024;
constexpr size_t kSplit1 = 512;
constexpr size_t kSplit2 = 256;
- alignas(Block*) byte bytes[kN];
+ alignas(BlockType*) byte bytes[kN];
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
- Block* block2 = nullptr;
- ASSERT_EQ(OkStatus(), block->Split(kSplit1, &block2));
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
- Block* block3 = nullptr;
- ASSERT_EQ(OkStatus(), block->Split(kSplit2, &block3));
+ result = BlockType::Split(block1, kSplit2);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block3 = *result;
- EXPECT_EQ(block->Next(), block3);
+ EXPECT_EQ(block1->Next(), block3);
+ EXPECT_EQ(block3->Prev(), block1);
EXPECT_EQ(block3->Next(), block2);
EXPECT_EQ(block2->Prev(), block3);
- EXPECT_EQ(block3->Prev(), block);
+}
+TEST(GenericBlockTest, CanSplitMidBlock) { CanSplitMidBlock<Block<>>(); }
+TEST(CustomBlockTest, CanSplitMidBlock) {
+ CanSplitMidBlock<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CannotSplitBlockWithoutHeaderSpace) {
- constexpr size_t kN = 1024;
- constexpr size_t kSplitN =
- kN - sizeof(Block) - 2 * PW_ALLOCATOR_POISON_OFFSET - 1;
- alignas(Block*) byte bytes[kN];
-
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+template <typename BlockType>
+void CannotSplitTooSmallBlock() {
+ constexpr size_t kN = 64;
+ constexpr size_t kSplitN = kN + 1;
+ alignas(BlockType*) byte bytes[kN];
- Block* next_block = nullptr;
- auto status = block->Split(kSplitN, &next_block);
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
- EXPECT_EQ(status, Status::ResourceExhausted());
- EXPECT_EQ(next_block, nullptr);
+ result = BlockType::Split(block, kSplitN);
+ EXPECT_EQ(result.status(), Status::OutOfRange());
}
-TEST(Block, MustProvideNextBlockPointer) {
+template <typename BlockType>
+void CannotSplitBlockWithoutHeaderSpace() {
constexpr size_t kN = 1024;
- constexpr size_t kSplitN = kN - sizeof(Block) - 1;
- alignas(Block*) byte bytes[kN];
+ constexpr size_t kSplitN = kN - BlockType::kBlockOverhead - 1;
+ alignas(BlockType*) byte bytes[kN];
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
- auto status = block->Split(kSplitN, nullptr);
- EXPECT_EQ(status, Status::InvalidArgument());
+ result = BlockType::Split(block, kSplitN);
+ EXPECT_EQ(result.status(), Status::ResourceExhausted());
+}
+TEST(GenericBlockTest, CannotSplitBlockWithoutHeaderSpace) {
+ CannotSplitBlockWithoutHeaderSpace<Block<>>();
+}
+TEST(CustomBlockTest, CannotSplitBlockWithoutHeaderSpace) {
+ CannotSplitBlockWithoutHeaderSpace<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CannotMakeBlockLargerInSplit) {
+template <typename BlockType>
+void CannotMakeBlockLargerInSplit() {
// Ensure that we can't ask for more space than the block actually has...
constexpr size_t kN = 1024;
- alignas(Block*) byte bytes[kN];
-
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ alignas(BlockType*) byte bytes[kN];
- Block* next_block = nullptr;
- auto status = block->Split(block->InnerSize() + 1, &next_block);
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
- EXPECT_EQ(status, Status::OutOfRange());
+ result = BlockType::Split(block, block->InnerSize() + 1);
+ EXPECT_EQ(result.status(), Status::OutOfRange());
+}
+TEST(GenericBlockTest, CannotMakeBlockLargerInSplit) {
+ CannotMakeBlockLargerInSplit<Block<>>();
+}
+TEST(CustomBlockTest, CannotMakeBlockLargerInSplit) {
+ CannotMakeBlockLargerInSplit<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CannotMakeSecondBlockLargerInSplit) {
+template <typename BlockType>
+void CannotMakeSecondBlockLargerInSplit() {
// Ensure that the second block in split is at least of the size of header.
constexpr size_t kN = 1024;
- alignas(Block*) byte bytes[kN];
+ alignas(BlockType*) byte bytes[kN];
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
- Block* next_block = nullptr;
- auto status = block->Split(
- block->InnerSize() - sizeof(Block) - 2 * PW_ALLOCATOR_POISON_OFFSET + 1,
- &next_block);
-
- ASSERT_EQ(status, Status::ResourceExhausted());
- EXPECT_EQ(next_block, nullptr);
+ result = BlockType::Split(block,
+ block->InnerSize() - BlockType::kBlockOverhead + 1);
+ EXPECT_EQ(result.status(), Status::ResourceExhausted());
+}
+TEST(GenericBlockTest, CannotMakeSecondBlockLargerInSplit) {
+ CannotMakeSecondBlockLargerInSplit<Block<>>();
+}
+TEST(CustomBlockTest, CannotMakeSecondBlockLargerInSplit) {
+ CannotMakeSecondBlockLargerInSplit<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CanMakeZeroSizeFirstBlock) {
+template <typename BlockType>
+void CanMakeZeroSizeFirstBlock() {
// This block does support splitting with zero payload size.
constexpr size_t kN = 1024;
- alignas(Block*) byte bytes[kN];
-
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ alignas(BlockType*) byte bytes[kN];
- Block* next_block = nullptr;
- auto status = block->Split(0, &next_block);
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
- ASSERT_EQ(status, OkStatus());
+ result = BlockType::Split(block, 0);
+ ASSERT_EQ(result.status(), OkStatus());
EXPECT_EQ(block->InnerSize(), static_cast<size_t>(0));
}
+TEST(GenericBlockTest, CanMakeZeroSizeFirstBlock) {
+ CanMakeZeroSizeFirstBlock<Block<>>();
+}
+TEST(CustomBlockTest, CanMakeZeroSizeFirstBlock) {
+ CanMakeZeroSizeFirstBlock<Block<uint32_t, kCapacity>>();
+}
-TEST(Block, CanMakeZeroSizeSecondBlock) {
+template <typename BlockType>
+void CanMakeZeroSizeSecondBlock() {
// Likewise, the split block can be zero-width.
constexpr size_t kN = 1024;
- alignas(Block*) byte bytes[kN];
+ alignas(BlockType*) byte bytes[kN];
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
- Block* next_block = nullptr;
- auto status = block->Split(
- block->InnerSize() - sizeof(Block) - 2 * PW_ALLOCATOR_POISON_OFFSET,
- &next_block);
+ result =
+ BlockType::Split(block1, block1->InnerSize() - BlockType::kBlockOverhead);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
- ASSERT_EQ(status, OkStatus());
- EXPECT_EQ(next_block->InnerSize(), static_cast<size_t>(0));
+ EXPECT_EQ(block2->InnerSize(), static_cast<size_t>(0));
+}
+TEST(GenericBlockTest, CanMakeZeroSizeSecondBlock) {
+ CanMakeZeroSizeSecondBlock<Block<>>();
+}
+TEST(CustomBlockTest, CanMakeZeroSizeSecondBlock) {
+ CanMakeZeroSizeSecondBlock<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CanMarkBlockUsed) {
+template <typename BlockType>
+void CanMarkBlockUsed() {
constexpr size_t kN = 1024;
- alignas(Block*) byte bytes[kN];
+ alignas(BlockType*) byte bytes[kN];
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
block->MarkUsed();
- EXPECT_EQ(block->Used(), true);
+ EXPECT_TRUE(block->Used());
- // Mark used packs that data into the next pointer. Check that it's still
- // valid
- EXPECT_EQ(block->Next(), (Block*)((uintptr_t)block + kN));
+ // Size should be unaffected.
+ EXPECT_EQ(block->OuterSize(), kN);
block->MarkFree();
- EXPECT_EQ(block->Used(), false);
+ EXPECT_FALSE(block->Used());
+}
+TEST(GenericBlockTest, CanMarkBlockUsed) { CanMarkBlockUsed<Block<>>(); }
+TEST(CustomBlockTest, CanMarkBlockUsed) {
+ CanMarkBlockUsed<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CannotSplitUsedBlock) {
+template <typename BlockType>
+void CannotSplitUsedBlock() {
constexpr size_t kN = 1024;
- alignas(Block*) byte bytes[kN];
+ constexpr size_t kSplitN = 512;
+ alignas(BlockType*) byte bytes[kN];
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
block->MarkUsed();
+ result = BlockType::Split(block, kSplitN);
+ EXPECT_EQ(result.status(), Status::FailedPrecondition());
+}
+TEST(GenericBlockTest, CannotSplitUsedBlock) {
+ CannotSplitUsedBlock<Block<>>();
+}
+TEST(CustomBlockTest, CannotSplitUsedBlock) {
+ CannotSplitUsedBlock<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanAllocFirstFromAlignedBlock() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSize = 256;
+ constexpr size_t kAlign = 32;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
+
+ // Make sure the block's usable space is aligned.
+ auto addr = reinterpret_cast<uintptr_t>(block->UsableSpace());
+ size_t pad_inner_size = AlignUp(addr, kAlign) - addr;
+ if (pad_inner_size != 0) {
+ if (pad_inner_size < BlockType::kHeaderSize) {
+ pad_inner_size += kAlign;
+ }
+ pad_inner_size -= BlockType::kHeaderSize;
+ result = BlockType::Split(block, pad_inner_size);
+ EXPECT_EQ(result.status(), OkStatus());
+ block = *result;
+ }
+
+ // Allocate from the front of the block.
+ BlockType* prev = block->Prev();
+ EXPECT_EQ(BlockType::AllocFirst(block, kSize, kAlign), OkStatus());
+ EXPECT_EQ(block->InnerSize(), kSize);
+ addr = reinterpret_cast<uintptr_t>(block->UsableSpace());
+ EXPECT_EQ(addr % kAlign, 0U);
+ EXPECT_TRUE(block->Used());
+
+ // No new padding block was allocated.
+ EXPECT_EQ(prev, block->Prev());
+
+ // Extra was split from the end of the block.
+ EXPECT_FALSE(block->Last());
+}
+TEST(GenericBlockTest, CanAllocFirstFromAlignedBlock) {
+ CanAllocFirstFromAlignedBlock<Block<>>();
+}
+TEST(CustomBlockTest, CanAllocFirstFromAlignedBlock) {
+ CanAllocFirstFromAlignedBlock<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanAllocFirstFromUnalignedBlock() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSize = 256;
+ constexpr size_t kAlign = 32;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
+
+ // Make sure the block's usable space is not aligned.
+ auto addr = reinterpret_cast<uintptr_t>(block->UsableSpace());
+ size_t pad_inner_size = AlignUp(addr, kAlign) - addr + (kAlign / 2);
+ if (pad_inner_size < BlockType::kHeaderSize) {
+ pad_inner_size += kAlign;
+ }
+ pad_inner_size -= BlockType::kHeaderSize;
+ result = BlockType::Split(block, pad_inner_size);
+ EXPECT_EQ(result.status(), OkStatus());
+ block = *result;
+
+ // Allocate from the front of the block.
+ BlockType* prev = block->Prev();
+ EXPECT_EQ(BlockType::AllocFirst(block, kSize, kAlign), OkStatus());
+ EXPECT_EQ(block->InnerSize(), kSize);
+ addr = reinterpret_cast<uintptr_t>(block->UsableSpace());
+ EXPECT_EQ(addr % kAlign, 0U);
+ EXPECT_TRUE(block->Used());
+
+ // A new padding block was allocated.
+ EXPECT_LT(prev, block->Prev());
+
+ // Extra was split from the end of the block.
+ EXPECT_FALSE(block->Last());
+}
+TEST(GenericBlockTest, CanAllocFirstFromUnalignedBlock) {
+ CanAllocFirstFromUnalignedBlock<Block<>>();
+}
+TEST(CustomBlockTest, CanAllocFirstFromUnalignedBlock) {
+ CanAllocFirstFromUnalignedBlock<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CannotAllocFirstTooSmallBlock() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kAlign = 32;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
+
+ // Make sure the block's usable space is not aligned.
+ auto addr = reinterpret_cast<uintptr_t>(block->UsableSpace());
+ size_t pad_inner_size = AlignUp(addr, kAlign) - addr + (kAlign / 2);
+ if (pad_inner_size < BlockType::kHeaderSize) {
+ pad_inner_size += kAlign;
+ }
+ pad_inner_size -= BlockType::kHeaderSize;
+ result = BlockType::Split(block, pad_inner_size);
+ EXPECT_EQ(result.status(), OkStatus());
+ block = *result;
+
+ // Cannot allocate without room to a split a block for alignment.
+ EXPECT_EQ(BlockType::AllocFirst(block, block->InnerSize(), kAlign),
+ Status::OutOfRange());
+}
+TEST(GenericBlockTest, CannotAllocFirstTooSmallBlock) {
+ CannotAllocFirstTooSmallBlock<Block<>>();
+}
+TEST(CustomBlockTest, CannotAllocFirstTooSmallBlock) {
+ CannotAllocFirstTooSmallBlock<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanAllocLast() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSize = 256;
+ constexpr size_t kAlign = 32;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
+
+ // Allocate from the back of the block.
+ EXPECT_EQ(BlockType::AllocLast(block, kSize, kAlign), OkStatus());
+ EXPECT_GE(block->InnerSize(), kSize);
+ auto addr = reinterpret_cast<uintptr_t>(block->UsableSpace());
+ EXPECT_EQ(addr % kAlign, 0U);
+ EXPECT_TRUE(block->Used());
+
+ // Extra was split from the front of the block.
+ EXPECT_FALSE(block->Prev()->Used());
+ EXPECT_TRUE(block->Last());
+}
+TEST(GenericBlockTest, CanAllocLast) { CanAllocLast<Block<>>(); }
+TEST(CustomBlockTest, CanAllocLast) {
+ CanAllocLast<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CannotAllocLastFromTooSmallBlock() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kAlign = 32;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
+
+ // Make sure the block's usable space is not aligned.
+ auto addr = reinterpret_cast<uintptr_t>(block->UsableSpace());
+ size_t pad_inner_size = AlignUp(addr, kAlign) - addr + (kAlign / 2);
+ if (pad_inner_size < BlockType::kHeaderSize) {
+ pad_inner_size += kAlign;
+ }
+ pad_inner_size -= BlockType::kHeaderSize;
+ result = BlockType::Split(block, pad_inner_size);
+ EXPECT_EQ(result.status(), OkStatus());
+ block = *result;
+
+ // Cannot allocate without room to a split a block for alignment.
+ EXPECT_EQ(BlockType::AllocLast(block, block->InnerSize(), kAlign),
+ Status::ResourceExhausted());
+}
- Block* next_block = nullptr;
- auto status = block->Split(512, &next_block);
- EXPECT_EQ(status, Status::FailedPrecondition());
+TEST(GenericBlockTest, CannotAllocLastFromTooSmallBlock) {
+ CannotAllocLastFromTooSmallBlock<Block<>>();
+}
+TEST(CustomBlockTest, CannotAllocLastFromTooSmallBlock) {
+ CannotAllocLastFromTooSmallBlock<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CanMergeWithNextBlock) {
+template <typename BlockType>
+void CanMergeWithNextBlock() {
// Do the three way merge from "CanSplitMidBlock", and let's
// merge block 3 and 2
constexpr size_t kN = 1024;
constexpr size_t kSplit1 = 512;
constexpr size_t kSplit2 = 256;
- alignas(Block*) byte bytes[kN];
-
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ alignas(BlockType*) byte bytes[kN];
- Block* block2 = nullptr;
- ASSERT_EQ(OkStatus(), block->Split(kSplit1, &block2));
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
- Block* block3 = nullptr;
- ASSERT_EQ(OkStatus(), block->Split(kSplit2, &block3));
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
- EXPECT_EQ(block3->MergeNext(), OkStatus());
+ result = BlockType::Split(block1, kSplit2);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block3 = *result;
- EXPECT_EQ(block->Next(), block3);
- EXPECT_EQ(block3->Prev(), block);
- EXPECT_EQ(block->InnerSize(), kSplit2);
+ EXPECT_EQ(BlockType::MergeNext(block3), OkStatus());
- // The resulting "right hand" block should have an outer size of 1024 - 256 -
- // sizeof(Block) - 2*PW_ALLOCATOR_POISON_OFFSET, which accounts for the first
- // block.
- EXPECT_EQ(block3->OuterSize(),
- kN - kSplit2 - sizeof(Block) - 2 * PW_ALLOCATOR_POISON_OFFSET);
+ EXPECT_EQ(block1->Next(), block3);
+ EXPECT_EQ(block3->Prev(), block1);
+ EXPECT_EQ(block1->InnerSize(), kSplit2);
+ EXPECT_EQ(block3->OuterSize(), kN - block1->OuterSize());
+}
+TEST(GenericBlockTest, CanMergeWithNextBlock) {
+ CanMergeWithNextBlock<Block<>>();
+}
+TEST(CustomBlockTest, CanMergeWithNextBlock) {
+ CanMergeWithNextBlock<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CannotMergeWithFirstOrLastBlock) {
+template <typename BlockType>
+void CannotMergeWithFirstOrLastBlock() {
constexpr size_t kN = 1024;
- alignas(Block*) byte bytes[kN];
+ constexpr size_t kSplitN = 512;
+ alignas(BlockType*) byte bytes[kN];
// Do a split, just to check that the checks on Next/Prev are
// different...
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
+
+ result = BlockType::Split(block1, kSplitN);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
- Block* next_block = nullptr;
- ASSERT_EQ(OkStatus(), block->Split(512, &next_block));
+ EXPECT_EQ(BlockType::MergeNext(block2), Status::OutOfRange());
- EXPECT_EQ(next_block->MergeNext(), Status::OutOfRange());
- EXPECT_EQ(block->MergePrev(), Status::OutOfRange());
+ BlockType* block0 = block1->Prev();
+ EXPECT_EQ(BlockType::MergeNext(block0), Status::OutOfRange());
+}
+TEST(GenericBlockTest, CannotMergeWithFirstOrLastBlock) {
+ CannotMergeWithFirstOrLastBlock<Block<>>();
+}
+TEST(CustomBlockTest, CannotMergeWithFirstOrLastBlock) {
+ CannotMergeWithFirstOrLastBlock<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CannotMergeUsedBlock) {
+template <typename BlockType>
+void CannotMergeUsedBlock() {
constexpr size_t kN = 1024;
- alignas(Block*) byte bytes[kN];
+ constexpr size_t kSplitN = 512;
+ alignas(BlockType*) byte bytes[kN];
// Do a split, just to check that the checks on Next/Prev are
// different...
- Block* block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &block), OkStatus());
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
+
+ result = BlockType::Split(block, kSplitN);
+ ASSERT_EQ(result.status(), OkStatus());
+
+ block->MarkUsed();
+ EXPECT_EQ(BlockType::MergeNext(block), Status::FailedPrecondition());
+}
+TEST(GenericBlockTest, CannotMergeUsedBlock) {
+ CannotMergeUsedBlock<Block<>>();
+}
+TEST(CustomBlockTest, CannotMergeUsedBlock) {
+ CannotMergeUsedBlock<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanFreeSingleBlock() {
+ constexpr size_t kN = 1024;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
+
+ block->MarkUsed();
+ BlockType::Free(block);
+ EXPECT_FALSE(block->Used());
+ EXPECT_EQ(block->OuterSize(), kN);
+}
+TEST(GenericBlockTest, CanFreeSingleBlock) { CanFreeSingleBlock<Block<>>(); }
+TEST(CustomBlockTest, CanFreeSingleBlock) {
+ CanFreeSingleBlock<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanFreeBlockWithoutMerging() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSplit1 = 512;
+ constexpr size_t kSplit2 = 256;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
+
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
+
+ result = BlockType::Split(block2, kSplit2);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block3 = *result;
+
+ block1->MarkUsed();
+ block2->MarkUsed();
+ block3->MarkUsed();
+
+ BlockType::Free(block2);
+ EXPECT_FALSE(block2->Used());
+ EXPECT_NE(block2->Prev(), nullptr);
+ EXPECT_FALSE(block2->Last());
+}
+TEST(GenericBlockTest, CanFreeBlockWithoutMerging) {
+ CanFreeBlockWithoutMerging<Block<>>();
+}
+TEST(CustomBlockTest, CanFreeBlockWithoutMerging) {
+ CanFreeBlockWithoutMerging<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanFreeBlockAndMergeWithPrev() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSplit1 = 512;
+ constexpr size_t kSplit2 = 256;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
+
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
+
+ result = BlockType::Split(block2, kSplit2);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block3 = *result;
+
+ block2->MarkUsed();
+ block3->MarkUsed();
+
+ BlockType::Free(block2);
+ EXPECT_FALSE(block2->Used());
+ EXPECT_EQ(block2->Prev(), nullptr);
+ EXPECT_FALSE(block2->Last());
+}
+TEST(GenericBlockTest, CanFreeBlockAndMergeWithPrev) {
+ CanFreeBlockAndMergeWithPrev<Block<>>();
+}
+TEST(CustomBlockTest, CanFreeBlockAndMergeWithPrev) {
+ CanFreeBlockAndMergeWithPrev<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanFreeBlockAndMergeWithNext() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSplit1 = 512;
+ constexpr size_t kSplit2 = 256;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
+
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
+
+ result = BlockType::Split(block2, kSplit2);
+ ASSERT_EQ(result.status(), OkStatus());
+
+ block1->MarkUsed();
+ block2->MarkUsed();
+
+ BlockType::Free(block2);
+ EXPECT_FALSE(block2->Used());
+ EXPECT_NE(block2->Prev(), nullptr);
+ EXPECT_TRUE(block2->Last());
+}
+TEST(GenericBlockTest, CanFreeBlockAndMergeWithNext) {
+ CanFreeBlockAndMergeWithNext<Block<>>();
+}
+TEST(CustomBlockTest, CanFreeBlockAndMergeWithNext) {
+ CanFreeBlockAndMergeWithNext<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanFreeUsedBlockAndMergeWithBoth() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSplit1 = 512;
+ constexpr size_t kSplit2 = 256;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
- Block* next_block = nullptr;
- ASSERT_EQ(OkStatus(), block->Split(512, &next_block));
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
+
+ result = BlockType::Split(block2, kSplit2);
+ ASSERT_EQ(result.status(), OkStatus());
+
+ block2->MarkUsed();
+
+ BlockType::Free(block2);
+ EXPECT_FALSE(block2->Used());
+ EXPECT_EQ(block2->Prev(), nullptr);
+ EXPECT_TRUE(block2->Last());
+}
+TEST(GenericBlockTest, CanFreeUsedBlockAndMergeWithBoth) {
+ CanFreeUsedBlockAndMergeWithBoth<Block<>>();
+}
+TEST(CustomBlockTest, CanFreeUsedBlockAndMergeWithBoth) {
+ CanFreeUsedBlockAndMergeWithBoth<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanResizeBlockSameSize() {
+ constexpr size_t kN = 1024;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
block->MarkUsed();
- EXPECT_EQ(block->MergeNext(), Status::FailedPrecondition());
- EXPECT_EQ(next_block->MergePrev(), Status::FailedPrecondition());
+ EXPECT_EQ(BlockType::Resize(block, block->InnerSize()), OkStatus());
+}
+TEST(GenericBlockTest, CanResizeBlockSameSize) {
+ CanResizeBlockSameSize<Block<>>();
+}
+TEST(CustomBlockTest, CanResizeBlockSameSize) {
+ CanResizeBlockSameSize<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CannotResizeFreeBlock() {
+ constexpr size_t kN = 1024;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
+
+ EXPECT_EQ(BlockType::Resize(block, block->InnerSize()),
+ Status::FailedPrecondition());
+}
+TEST(GenericBlockTest, CannotResizeFreeBlock) {
+ CannotResizeFreeBlock<Block<>>();
+}
+TEST(CustomBlockTest, CannotResizeFreeBlock) {
+ CannotResizeFreeBlock<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanResizeBlockSmallerWithNextFree() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSplit1 = 512;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
+
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
+
+ block1->MarkUsed();
+ size_t block2_inner_size = block2->InnerSize();
+
+ // Shrink by less than the minimum needed for a block. The extra should be
+ // added to the subsequent block.
+ size_t delta = BlockType::kBlockOverhead / 2;
+ size_t new_inner_size = block1->InnerSize() - delta;
+ EXPECT_EQ(BlockType::Resize(block1, new_inner_size), OkStatus());
+ EXPECT_EQ(block1->InnerSize(), new_inner_size);
+
+ block2 = block1->Next();
+ EXPECT_GE(block2->InnerSize(), block2_inner_size + delta);
+}
+TEST(GenericBlockTest, CanResizeBlockSmallerWithNextFree) {
+ CanResizeBlockSmallerWithNextFree<Block<>>();
+}
+TEST(CustomBlockTest, CanResizeBlockSmallerWithNextFree) {
+ CanResizeBlockSmallerWithNextFree<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanResizeBlockLargerWithNextFree() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSplit1 = 512;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
+
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
+
+ block1->MarkUsed();
+ size_t block2_inner_size = block2->InnerSize();
+
+ // Grow by less than the minimum needed for a block. The extra should be
+ // added to the subsequent block.
+ size_t delta = BlockType::kBlockOverhead / 2;
+ size_t new_inner_size = block1->InnerSize() + delta;
+ EXPECT_EQ(BlockType::Resize(block1, new_inner_size), OkStatus());
+ EXPECT_EQ(block1->InnerSize(), new_inner_size);
+
+ block2 = block1->Next();
+ EXPECT_GE(block2->InnerSize(), block2_inner_size - delta);
+}
+TEST(GenericBlockTest, CanResizeBlockLargerWithNextFree) {
+ CanResizeBlockLargerWithNextFree<Block<>>();
+}
+TEST(CustomBlockTest, CanResizeBlockLargerWithNextFree) {
+ CanResizeBlockLargerWithNextFree<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CannotResizeBlockMuchLargerWithNextFree() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSplit1 = 512;
+ constexpr size_t kSplit2 = 256;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
+
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
+
+ result = BlockType::Split(block2, kSplit2);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block3 = *result;
+
+ block1->MarkUsed();
+ block3->MarkUsed();
+
+ size_t new_inner_size = block1->InnerSize() + block2->OuterSize() + 1;
+ EXPECT_EQ(BlockType::Resize(block1, new_inner_size), Status::OutOfRange());
+}
+TEST(GenericBlockTest, CannotResizeBlockMuchLargerWithNextFree) {
+ CannotResizeBlockMuchLargerWithNextFree<Block<>>();
+}
+TEST(CustomBlockTest, CannotResizeBlockMuchLargerWithNextFree) {
+ CannotResizeBlockMuchLargerWithNextFree<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CanCheckValidBlock) {
+template <typename BlockType>
+void CanResizeBlockSmallerWithNextUsed() {
constexpr size_t kN = 1024;
- alignas(Block*) byte bytes[kN];
+ constexpr size_t kSplit1 = 512;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
- Block* first_block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &first_block), OkStatus());
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
- Block* second_block = nullptr;
- ASSERT_EQ(OkStatus(), first_block->Split(512, &second_block));
+ block1->MarkUsed();
+ block2->MarkUsed();
- Block* third_block = nullptr;
- ASSERT_EQ(OkStatus(), second_block->Split(256, &third_block));
+ // Shrink the block.
+ size_t delta = kSplit1 / 2;
+ size_t new_inner_size = block1->InnerSize() - delta;
+ EXPECT_EQ(BlockType::Resize(block1, new_inner_size), OkStatus());
+ EXPECT_EQ(block1->InnerSize(), new_inner_size);
- EXPECT_EQ(first_block->IsValid(), true);
- EXPECT_EQ(second_block->IsValid(), true);
- EXPECT_EQ(third_block->IsValid(), true);
+ block2 = block1->Next();
+ EXPECT_EQ(block2->OuterSize(), delta);
+}
+TEST(GenericBlockTest, CanResizeBlockSmallerWithNextUsed) {
+ CanResizeBlockSmallerWithNextUsed<Block<>>();
+}
+TEST(CustomBlockTest, CanResizeBlockSmallerWithNextUsed) {
+ CanResizeBlockSmallerWithNextUsed<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CanCheckInalidBlock) {
+template <typename BlockType>
+void CannotResizeBlockLargerWithNextUsed() {
constexpr size_t kN = 1024;
- alignas(Block*) byte bytes[kN];
+ constexpr size_t kSplit1 = 512;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
- Block* first_block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &first_block), OkStatus());
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
- Block* second_block = nullptr;
- ASSERT_EQ(OkStatus(), first_block->Split(512, &second_block));
+ block1->MarkUsed();
+ block2->MarkUsed();
- Block* third_block = nullptr;
- ASSERT_EQ(OkStatus(), second_block->Split(256, &third_block));
+ size_t delta = BlockType::kBlockOverhead / 2;
+ size_t new_inner_size = block1->InnerSize() + delta;
+ EXPECT_EQ(BlockType::Resize(block1, new_inner_size), Status::OutOfRange());
+}
+TEST(GenericBlockTest, CannotResizeBlockLargerWithNextUsed) {
+ CannotResizeBlockLargerWithNextUsed<Block<>>();
+}
+TEST(CustomBlockTest, CannotResizeBlockLargerWithNextUsed) {
+ CannotResizeBlockLargerWithNextUsed<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanCheckValidBlock() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSplit1 = 512;
+ constexpr size_t kSplit2 = 256;
+ alignas(BlockType*) byte bytes[kN];
- Block* fourth_block = nullptr;
- ASSERT_EQ(OkStatus(), third_block->Split(128, &fourth_block));
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
- std::byte* next_ptr = reinterpret_cast<std::byte*>(first_block);
- memcpy(next_ptr, second_block, sizeof(void*));
- EXPECT_EQ(first_block->IsValid(), false);
- EXPECT_EQ(second_block->IsValid(), false);
- EXPECT_EQ(third_block->IsValid(), true);
- EXPECT_EQ(fourth_block->IsValid(), true);
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
+
+ result = BlockType::Split(block2, kSplit2);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block3 = *result;
+
+ EXPECT_TRUE(block1->IsValid());
+ block1->CrashIfInvalid();
+
+ EXPECT_TRUE(block2->IsValid());
+ block2->CrashIfInvalid();
+
+ EXPECT_TRUE(block3->IsValid());
+ block3->CrashIfInvalid();
+}
+TEST(GenericBlockTest, CanCheckValidBlock) { CanCheckValidBlock<Block<>>(); }
+TEST(CustomBlockTest, CanCheckValidBlock) {
+ CanCheckValidBlock<Block<uint32_t, kCapacity>>();
+}
+
+template <typename BlockType>
+void CanCheckInvalidBlock() {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSplit1 = 512;
+ constexpr size_t kSplit2 = 128;
+ constexpr size_t kSplit3 = 256;
+ alignas(BlockType*) byte bytes[kN];
+ memset(bytes, 0, sizeof(bytes));
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
+
+ result = BlockType::Split(block1, kSplit1);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
+
+ result = BlockType::Split(block2, kSplit2);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block3 = *result;
+
+ result = BlockType::Split(block3, kSplit3);
+ ASSERT_EQ(result.status(), OkStatus());
#if defined(PW_ALLOCATOR_POISON_ENABLE) && PW_ALLOCATOR_POISON_ENABLE
- std::byte fault_poison[PW_ALLOCATOR_POISON_OFFSET] = {std::byte(0)};
- std::byte* front_poison =
- reinterpret_cast<std::byte*>(third_block) + sizeof(*third_block);
- memcpy(front_poison, fault_poison, PW_ALLOCATOR_POISON_OFFSET);
- EXPECT_EQ(third_block->IsValid(), false);
-
- std::byte* end_poison =
- reinterpret_cast<std::byte*>(fourth_block) + sizeof(*fourth_block);
- memcpy(end_poison, fault_poison, PW_ALLOCATOR_POISON_OFFSET);
- EXPECT_EQ(fourth_block->IsValid(), false);
+ // Corrupt a byte in the poisoned header.
+ EXPECT_TRUE(block1->IsValid());
+ bytes[BlockType::kHeaderSize - 1] = std::byte(0xFF);
+ EXPECT_FALSE(block1->IsValid());
+
+ // Corrupt a byte in the poisoned footer.
+ EXPECT_TRUE(block2->IsValid());
+ bytes[block1->OuterSize() + block2->OuterSize() - 1] = std::byte(0xFF);
+ EXPECT_FALSE(block2->IsValid());
#endif // PW_ALLOCATOR_POISON_ENABLE
+
+ // Corrupt a Block header.
+ // This must not touch memory outside the original region, or the test may
+ // (correctly) abort when run with address sanitizer.
+ // To remain as agostic to the internals of `Block` as possible, the test
+ // copies a smaller block's header to a larger block.
+ EXPECT_TRUE(block3->IsValid());
+ auto* src = reinterpret_cast<std::byte*>(block2);
+ auto* dst = reinterpret_cast<std::byte*>(block3);
+ std::memcpy(dst, src, sizeof(BlockType));
+ EXPECT_FALSE(block3->IsValid());
+}
+TEST(GenericBlockTest, CanCheckInvalidBlock) {
+ CanCheckInvalidBlock<Block<>>();
+}
+TEST(CustomBlockTest, CanCheckInvalidBlock) {
+ CanCheckInvalidBlock<Block<uint32_t, kCapacity>>();
}
-TEST(Block, CanPoisonBlock) {
-#if defined(PW_ALLOCATOR_POISON_ENABLE) && PW_ALLOCATOR_POISON_ENABLE
+TEST(CustomBlockTest, CustomFlagsInitiallyZero) {
constexpr size_t kN = 1024;
- alignas(Block*) byte bytes[kN];
+ using BlockType = Block<uint16_t, kN>;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
- Block* first_block = nullptr;
- EXPECT_EQ(Block::Init(span(bytes, kN), &first_block), OkStatus());
+ EXPECT_EQ(block->GetFlags(), 0U);
+}
- Block* second_block = nullptr;
- ASSERT_EQ(OkStatus(), first_block->Split(512, &second_block));
+TEST(CustomBlockTest, SetCustomFlags) {
+ constexpr size_t kN = 1024;
+ using BlockType = Block<uint16_t, kN>;
+ alignas(BlockType*) byte bytes[kN];
- Block* third_block = nullptr;
- ASSERT_EQ(OkStatus(), second_block->Split(256, &third_block));
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
- EXPECT_EQ(first_block->IsValid(), true);
- EXPECT_EQ(second_block->IsValid(), true);
- EXPECT_EQ(third_block->IsValid(), true);
-#endif // PW_ALLOCATOR_POISON_ENABLE
+ block->SetFlags(1);
+ EXPECT_EQ(block->GetFlags(), 1U);
+}
+
+TEST(CustomBlockTest, SetAllCustomFlags) {
+ constexpr size_t kN = 1024;
+ using BlockType = Block<uint16_t, kN>;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
+
+ // `1024/alignof(uint16_t)` is `0x200`, which leaves 6 bits available for
+ // flags per offset field. After 1 builtin field, this leaves 2*5 available
+ // for custom flags.
+ block->SetFlags((uint16_t(1) << 10) - 1);
+ EXPECT_EQ(block->GetFlags(), 0x3FFU);
+}
+
+TEST(CustomBlockTest, ClearCustomFlags) {
+ constexpr size_t kN = 1024;
+ using BlockType = Block<uint16_t, kN>;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
+
+ block->SetFlags(0x155);
+ block->SetFlags(0x2AA, 0x333);
+ EXPECT_EQ(block->GetFlags(), 0x2EEU);
+}
+
+TEST(CustomBlockTest, FlagsNotCopiedOnSplit) {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSplitN = 512;
+ using BlockType = Block<uint16_t, kN>;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block1 = *result;
+ block1->SetFlags(0x137);
+
+ result = BlockType::Split(block1, kSplitN);
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block2 = *result;
+
+ EXPECT_EQ(block1->GetFlags(), 0x137U);
+ EXPECT_EQ(block2->GetFlags(), 0U);
+}
+
+TEST(CustomBlockTest, FlagsPreservedByMergeNext) {
+ constexpr size_t kN = 1024;
+ constexpr size_t kSplitN = 512;
+ using BlockType = Block<uint16_t, kN>;
+ alignas(BlockType*) byte bytes[kN];
+
+ Result<BlockType*> result = BlockType::Init(span(bytes, kN));
+ ASSERT_EQ(result.status(), OkStatus());
+ BlockType* block = *result;
+
+ result = BlockType::Split(block, kSplitN);
+ ASSERT_EQ(result.status(), OkStatus());
+
+ block->SetFlags(0x137);
+ EXPECT_EQ(BlockType::MergeNext(block), OkStatus());
+ EXPECT_EQ(block->GetFlags(), 0x137U);
}
} // namespace pw::allocator