| // Copyright 2020 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_allocator/block.h" |
| |
| #include <array> |
| #include <cstdint> |
| #include <cstring> |
| |
| #include "pw_span/span.h" |
| #include "pw_unit_test/framework.h" |
| |
| namespace { |
| |
| // Test fixtures. |
| using ::pw::allocator::Layout; |
| using LargeOffsetBlock = ::pw::allocator::Block<uint64_t>; |
| using SmallOffsetBlock = ::pw::allocator::Block<uint16_t>; |
| using PoisonedBlock = ::pw::allocator::Block<uint32_t, alignof(uint32_t), true>; |
| |
| // Macro to provide type-parameterized tests for the various block types above. |
| // |
| // Ideally, the unit tests below could just use `TYPED_TEST_P` and its |
| // asscoiated macros from GoogleTest, see |
| // https://github.com/google/googletest/blob/main/docs/advanced.md#type-parameterized-tests |
| // |
| // These macros are not supported by the light framework however, so this macro |
| // provides a custom implementation that works just for these types. |
| #define TEST_FOR_EACH_BLOCK_TYPE(TestCase) \ |
| template <typename BlockType> \ |
| void TestCase(); \ |
| TEST(LargeOffsetBlockTest, TestCase) { TestCase<LargeOffsetBlock>(); } \ |
| TEST(SmallOffsetBlockTest, TestCase) { TestCase<SmallOffsetBlock>(); } \ |
| TEST(PoisonedBlockTest, TestCase) { TestCase<PoisonedBlock>(); } \ |
| template <typename BlockType> \ |
| void TestCase() |
| |
| // Unit tests. |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanCreateSingleAlignedBlock) { |
| constexpr size_t kN = 1024; |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| EXPECT_EQ(block->OuterSize(), kN); |
| EXPECT_EQ(block->InnerSize(), kN - BlockType::kBlockOverhead); |
| EXPECT_EQ(block->Prev(), nullptr); |
| EXPECT_EQ(block->Next(), nullptr); |
| EXPECT_FALSE(block->Used()); |
| EXPECT_TRUE(block->Last()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanCreateUnalignedSingleBlock) { |
| constexpr size_t kN = 1024; |
| |
| // Force alignment, so we can un-force it below |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| pw::ByteSpan aligned(bytes); |
| |
| auto result = BlockType::Init(aligned.subspan(1)); |
| EXPECT_EQ(result.status(), pw::OkStatus()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotCreateTooSmallBlock) { |
| std::array<std::byte, 2> bytes; |
| auto result = BlockType::Init(bytes); |
| EXPECT_FALSE(result.ok()); |
| EXPECT_EQ(result.status(), pw::Status::ResourceExhausted()); |
| } |
| |
| TEST(BlockTest, CannotCreateTooLargeBlock) { |
| using BlockType = ::pw::allocator::Block<uint8_t>; |
| constexpr size_t kN = 1024; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| pw::Result<BlockType*> result = BlockType::Init(bytes); |
| EXPECT_EQ(result.status(), pw::Status::OutOfRange()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanSplitBlock) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplitN = 512; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| auto* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplitN); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| |
| auto* block2 = *result; |
| |
| EXPECT_EQ(block1->InnerSize(), kSplitN); |
| EXPECT_EQ(block1->OuterSize(), kSplitN + BlockType::kBlockOverhead); |
| EXPECT_FALSE(block1->Last()); |
| |
| EXPECT_EQ(block2->OuterSize(), kN - kSplitN - BlockType::kBlockOverhead); |
| EXPECT_FALSE(block2->Used()); |
| EXPECT_TRUE(block2->Last()); |
| |
| EXPECT_EQ(block1->Next(), block2); |
| EXPECT_EQ(block2->Prev(), block1); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanSplitBlockUnaligned) { |
| constexpr size_t kN = 1024; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| // We should split at sizeof(BlockType) + kSplitN bytes. Then |
| // we need to round that up to an alignof(BlockType) boundary. |
| constexpr size_t kSplitN = 513; |
| uintptr_t split_addr = reinterpret_cast<uintptr_t>(block1) + kSplitN; |
| split_addr += alignof(BlockType) - (split_addr % alignof(BlockType)); |
| uintptr_t split_len = split_addr - (uintptr_t)&bytes; |
| |
| result = BlockType::Split(block1, kSplitN); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| EXPECT_EQ(block1->InnerSize(), split_len); |
| EXPECT_EQ(block1->OuterSize(), split_len + BlockType::kBlockOverhead); |
| |
| EXPECT_EQ(block2->OuterSize(), kN - block1->OuterSize()); |
| EXPECT_FALSE(block2->Used()); |
| |
| EXPECT_EQ(block1->Next(), block2); |
| EXPECT_EQ(block2->Prev(), block1); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanSplitMidBlock) { |
| // Split once, then split the original block again to ensure that the |
| // pointers get rewired properly. |
| // I.e. |
| // [[ BLOCK 1 ]] |
| // block1->Split() |
| // [[ BLOCK1 ]][[ BLOCK2 ]] |
| // block1->Split() |
| // [[ BLOCK1 ]][[ BLOCK3 ]][[ BLOCK2 ]] |
| |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 512; |
| constexpr size_t kSplit2 = 256; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| result = BlockType::Split(block1, kSplit2); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block3 = *result; |
| |
| EXPECT_EQ(block1->Next(), block3); |
| EXPECT_EQ(block3->Prev(), block1); |
| EXPECT_EQ(block3->Next(), block2); |
| EXPECT_EQ(block2->Prev(), block3); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotSplitTooSmallBlock) { |
| constexpr size_t kN = 64; |
| constexpr size_t kSplitN = kN + 1; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| result = BlockType::Split(block, kSplitN); |
| EXPECT_EQ(result.status(), pw::Status::OutOfRange()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotSplitBlockWithoutHeaderSpace) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplitN = kN - BlockType::kBlockOverhead - 1; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| result = BlockType::Split(block, kSplitN); |
| EXPECT_EQ(result.status(), pw::Status::ResourceExhausted()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotSplitNull) { |
| BlockType* block = nullptr; |
| auto result = BlockType::Split(block, 1); |
| EXPECT_EQ(result.status(), pw::Status::InvalidArgument()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotMakeBlockLargerInSplit) { |
| // Ensure that we can't ask for more space than the block actually has... |
| constexpr size_t kN = 1024; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| result = BlockType::Split(block, block->InnerSize() + 1); |
| EXPECT_EQ(result.status(), pw::Status::OutOfRange()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotMakeSecondBlockLargerInSplit) { |
| // Ensure that the second block in split is at least of the size of header. |
| constexpr size_t kN = 1024; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| result = BlockType::Split(block, |
| block->InnerSize() - BlockType::kBlockOverhead + 1); |
| EXPECT_EQ(result.status(), pw::Status::ResourceExhausted()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanMakeZeroSizeFirstBlock) { |
| // This block does support splitting with zero payload size. |
| constexpr size_t kN = 1024; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| result = BlockType::Split(block, 0); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| EXPECT_EQ(block->InnerSize(), static_cast<size_t>(0)); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanMakeZeroSizeSecondBlock) { |
| // Likewise, the split block can be zero-width. |
| constexpr size_t kN = 1024; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = |
| BlockType::Split(block1, block1->InnerSize() - BlockType::kBlockOverhead); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| EXPECT_EQ(block2->InnerSize(), static_cast<size_t>(0)); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanMarkBlockUsed) { |
| constexpr size_t kN = 1024; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| block->MarkUsed(); |
| EXPECT_TRUE(block->Used()); |
| |
| // Size should be unaffected. |
| EXPECT_EQ(block->OuterSize(), kN); |
| |
| block->MarkFree(); |
| EXPECT_FALSE(block->Used()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotSplitUsedBlock) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplitN = 512; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| block->MarkUsed(); |
| result = BlockType::Split(block, kSplitN); |
| EXPECT_EQ(result.status(), pw::Status::FailedPrecondition()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanAllocFirstFromAlignedBlock) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSize = 256; |
| constexpr size_t kAlign = 32; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| // Make sure the block's usable space is aligned. |
| auto addr = reinterpret_cast<uintptr_t>(block->UsableSpace()); |
| size_t pad_outer_size = pw::AlignUp(addr, kAlign) - addr; |
| if (pad_outer_size != 0) { |
| while (pad_outer_size < BlockType::kBlockOverhead) { |
| pad_outer_size += kAlign; |
| } |
| result = |
| BlockType::Split(block, pad_outer_size - BlockType::kBlockOverhead); |
| EXPECT_EQ(result.status(), pw::OkStatus()); |
| block = *result; |
| } |
| |
| // Allocate from the front of the block. |
| BlockType* prev = block->Prev(); |
| Layout layout(kSize, kAlign); |
| EXPECT_EQ(BlockType::AllocFirst(block, layout), pw::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_FOR_EACH_BLOCK_TYPE(CanAllocFirstFromUnalignedBlock) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSize = 256; |
| constexpr size_t kAlign = 32; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::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 = pw::AlignUp(addr, kAlign) - addr + (kAlign / 2); |
| if (pad_inner_size < BlockType::kBlockOverhead) { |
| pad_inner_size += kAlign; |
| } |
| pad_inner_size -= BlockType::kBlockOverhead; |
| result = BlockType::Split(block, pad_inner_size); |
| EXPECT_EQ(result.status(), pw::OkStatus()); |
| block = *result; |
| |
| BlockType* prev = block->Prev(); |
| prev->MarkUsed(); |
| size_t prev_inner_size = prev->InnerSize(); |
| |
| // Allocate from the front of the block. |
| Layout layout(kSize, kAlign); |
| EXPECT_EQ(BlockType::AllocFirst(block, layout), pw::OkStatus()); |
| EXPECT_EQ(block->InnerSize(), kSize); |
| addr = reinterpret_cast<uintptr_t>(block->UsableSpace()); |
| EXPECT_EQ(addr % kAlign, 0U); |
| EXPECT_TRUE(block->Used()); |
| |
| if (!block->Prev()->Used()) { |
| // Either a new free block was added... |
| EXPECT_LT(prev, block->Prev()); |
| } else { |
| /// ...or less than a minimum block was added to the previous block. |
| EXPECT_NE(prev_inner_size, prev->InnerSize()); |
| EXPECT_LT(prev->InnerSize() - prev_inner_size, BlockType::kBlockOverhead); |
| } |
| |
| // Extra was split from the end of the block. |
| EXPECT_FALSE(block->Last()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotAllocFirstTooSmallBlock) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kAlign = 32; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::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 = pw::AlignUp(addr, kAlign) - addr + (kAlign / 2); |
| if (pad_inner_size < BlockType::kBlockOverhead) { |
| pad_inner_size += kAlign; |
| } |
| pad_inner_size -= BlockType::kBlockOverhead; |
| result = BlockType::Split(block, pad_inner_size); |
| EXPECT_EQ(result.status(), pw::OkStatus()); |
| block = *result; |
| |
| // Cannot allocate without room to a split a block for alignment. |
| Layout layout(block->InnerSize(), kAlign); |
| EXPECT_EQ(BlockType::AllocFirst(block, layout), pw::Status::OutOfRange()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotAllocFirstFromNull) { |
| BlockType* block = nullptr; |
| Layout layout(1, 1); |
| EXPECT_EQ(BlockType::AllocFirst(block, layout), |
| pw::Status::InvalidArgument()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanAllocLast) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSize = 256; |
| constexpr size_t kAlign = 32; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| // Allocate from the back of the block. |
| Layout layout(kSize, kAlign); |
| EXPECT_EQ(BlockType::AllocLast(block, layout), pw::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_FOR_EACH_BLOCK_TYPE(CannotAllocLastFromTooSmallBlock) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kAlign = 32; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::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 = pw::AlignUp(addr, kAlign) - addr + (kAlign / 2); |
| if (pad_inner_size < BlockType::kBlockOverhead) { |
| pad_inner_size += kAlign; |
| } |
| pad_inner_size -= BlockType::kBlockOverhead; |
| result = BlockType::Split(block, pad_inner_size); |
| EXPECT_EQ(result.status(), pw::OkStatus()); |
| block = *result; |
| |
| // Cannot allocate without room to a split a block for alignment. |
| Layout layout(block->InnerSize(), kAlign); |
| EXPECT_EQ(BlockType::AllocLast(block, layout), |
| pw::Status::ResourceExhausted()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotAllocLastFromNull) { |
| BlockType* block = nullptr; |
| Layout layout(1, 1); |
| EXPECT_EQ(BlockType::AllocLast(block, layout), pw::Status::InvalidArgument()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(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(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| |
| result = BlockType::Split(block1, kSplit2); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block3 = *result; |
| |
| EXPECT_EQ(BlockType::MergeNext(block3), pw::OkStatus()); |
| |
| EXPECT_EQ(block1->Next(), block3); |
| EXPECT_EQ(block3->Prev(), block1); |
| EXPECT_EQ(block1->InnerSize(), kSplit2); |
| EXPECT_EQ(block3->OuterSize(), kN - block1->OuterSize()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotMergeWithFirstOrLastBlock) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplitN = 512; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| // Do a split, just to check that the checks on Next/Prev are different... |
| result = BlockType::Split(block1, kSplitN); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| EXPECT_EQ(BlockType::MergeNext(block2), pw::Status::OutOfRange()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotMergeNull) { |
| BlockType* block = nullptr; |
| EXPECT_EQ(BlockType::MergeNext(block), pw::Status::InvalidArgument()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotMergeUsedBlock) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplitN = 512; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| // Do a split, just to check that the checks on Next/Prev are different... |
| result = BlockType::Split(block, kSplitN); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| |
| block->MarkUsed(); |
| EXPECT_EQ(BlockType::MergeNext(block), pw::Status::FailedPrecondition()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanFreeSingleBlock) { |
| constexpr size_t kN = 1024; |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| block->MarkUsed(); |
| BlockType::Free(block); |
| EXPECT_FALSE(block->Used()); |
| EXPECT_EQ(block->OuterSize(), kN); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanFreeBlockWithoutMerging) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 512; |
| constexpr size_t kSplit2 = 256; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| result = BlockType::Split(block2, kSplit2); |
| ASSERT_EQ(result.status(), pw::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_FOR_EACH_BLOCK_TYPE(CanFreeBlockAndMergeWithPrev) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 512; |
| constexpr size_t kSplit2 = 256; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| result = BlockType::Split(block2, kSplit2); |
| ASSERT_EQ(result.status(), pw::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_FOR_EACH_BLOCK_TYPE(CanFreeBlockAndMergeWithNext) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 512; |
| constexpr size_t kSplit2 = 256; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| result = BlockType::Split(block2, kSplit2); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| |
| block1->MarkUsed(); |
| block2->MarkUsed(); |
| |
| BlockType::Free(block2); |
| EXPECT_FALSE(block2->Used()); |
| EXPECT_NE(block2->Prev(), nullptr); |
| EXPECT_TRUE(block2->Last()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanFreeUsedBlockAndMergeWithBoth) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 512; |
| constexpr size_t kSplit2 = 256; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| result = BlockType::Split(block2, kSplit2); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| |
| block2->MarkUsed(); |
| |
| BlockType::Free(block2); |
| EXPECT_FALSE(block2->Used()); |
| EXPECT_EQ(block2->Prev(), nullptr); |
| EXPECT_TRUE(block2->Last()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanResizeBlockSameSize) { |
| constexpr size_t kN = 1024; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| block->MarkUsed(); |
| EXPECT_EQ(BlockType::Resize(block, block->InnerSize()), pw::OkStatus()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotResizeFreeBlock) { |
| constexpr size_t kN = 1024; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block = *result; |
| |
| EXPECT_EQ(BlockType::Resize(block, block->InnerSize()), |
| pw::Status::FailedPrecondition()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanResizeBlockSmallerWithNextFree) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 512; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::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 - BlockType::kAlignment; |
| size_t new_inner_size = block1->InnerSize() - delta; |
| EXPECT_EQ(BlockType::Resize(block1, new_inner_size), pw::OkStatus()); |
| EXPECT_EQ(block1->InnerSize(), new_inner_size); |
| |
| block2 = block1->Next(); |
| EXPECT_GE(block2->InnerSize(), block2_inner_size + delta); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanResizeBlockLargerWithNextFree) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 512; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::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 - BlockType::kAlignment; |
| size_t new_inner_size = block1->InnerSize() + delta; |
| EXPECT_EQ(BlockType::Resize(block1, new_inner_size), pw::OkStatus()); |
| EXPECT_EQ(block1->InnerSize(), new_inner_size); |
| |
| block2 = block1->Next(); |
| EXPECT_GE(block2->InnerSize(), block2_inner_size - delta); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotResizeBlockMuchLargerWithNextFree) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 512; |
| constexpr size_t kSplit2 = 256; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| result = BlockType::Split(block2, kSplit2); |
| ASSERT_EQ(result.status(), pw::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), |
| pw::Status::OutOfRange()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanResizeBlockSmallerWithNextUsed) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 512; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| block1->MarkUsed(); |
| block2->MarkUsed(); |
| |
| // Shrink the block. |
| size_t delta = kSplit1 / 2; |
| size_t new_inner_size = block1->InnerSize() - delta; |
| EXPECT_EQ(BlockType::Resize(block1, new_inner_size), pw::OkStatus()); |
| EXPECT_EQ(block1->InnerSize(), new_inner_size); |
| |
| block2 = block1->Next(); |
| EXPECT_EQ(block2->OuterSize(), delta); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotResizeBlockLargerWithNextUsed) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 512; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| block1->MarkUsed(); |
| block2->MarkUsed(); |
| |
| size_t delta = BlockType::kBlockOverhead / 2; |
| size_t new_inner_size = block1->InnerSize() + delta; |
| EXPECT_EQ(BlockType::Resize(block1, new_inner_size), |
| pw::Status::OutOfRange()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CannotResizeFromTooNull) { |
| BlockType* block = nullptr; |
| EXPECT_EQ(BlockType::Resize(block, 1), pw::Status::InvalidArgument()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanCheckValidBlock) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 512; |
| constexpr size_t kSplit2 = 256; |
| |
| alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| result = BlockType::Split(block2, kSplit2); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block3 = *result; |
| |
| EXPECT_TRUE(block1->IsValid()); |
| block1->CrashIfInvalid(); |
| |
| EXPECT_TRUE(block2->IsValid()); |
| block2->CrashIfInvalid(); |
| |
| EXPECT_TRUE(block3->IsValid()); |
| block3->CrashIfInvalid(); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanCheckInvalidBlock) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 128; |
| constexpr size_t kSplit2 = 384; |
| constexpr size_t kSplit3 = 256; |
| |
| std::array<std::byte, kN> bytes{}; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| result = BlockType::Split(block1, kSplit1); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block2 = *result; |
| |
| result = BlockType::Split(block2, kSplit2); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block3 = *result; |
| |
| result = BlockType::Split(block3, kSplit3); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| |
| // 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(block1->IsValid()); |
| EXPECT_TRUE(block2->IsValid()); |
| EXPECT_TRUE(block3->IsValid()); |
| auto* src = reinterpret_cast<std::byte*>(block1); |
| auto* dst = reinterpret_cast<std::byte*>(block2); |
| std::memcpy(dst, src, sizeof(BlockType)); |
| EXPECT_FALSE(block1->IsValid()); |
| EXPECT_FALSE(block2->IsValid()); |
| EXPECT_FALSE(block3->IsValid()); |
| } |
| |
| TEST(PoisonedBlockTest, CanCheckPoison) { |
| constexpr size_t kN = 1024; |
| // constexpr size_t kSplit1 = 512; |
| std::array<std::byte, kN> bytes{}; |
| auto result = PoisonedBlock::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| PoisonedBlock* block = *result; |
| |
| // Modify a byte in the middle of a free block. |
| // Without poisoning, the modification is undetected. |
| EXPECT_FALSE(block->Used()); |
| bytes[kN / 2] = std::byte(0x7f); |
| EXPECT_TRUE(block->IsValid()); |
| |
| // Modify a byte in the middle of a free block. |
| // With poisoning, the modification is detected. |
| block->Poison(); |
| bytes[kN / 2] = std::byte(0x7f); |
| EXPECT_FALSE(block->IsValid()); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanGetBlockFromUsableSpace) { |
| constexpr size_t kN = 1024; |
| |
| std::array<std::byte, kN> bytes{}; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| void* ptr = block1->UsableSpace(); |
| BlockType* block2 = BlockType::FromUsableSpace(ptr); |
| EXPECT_EQ(block1, block2); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanGetConstBlockFromUsableSpace) { |
| constexpr size_t kN = 1024; |
| |
| std::array<std::byte, kN> bytes{}; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| const BlockType* block1 = *result; |
| |
| const void* ptr = block1->UsableSpace(); |
| const BlockType* block2 = BlockType::FromUsableSpace(ptr); |
| EXPECT_EQ(block1, block2); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(CanGetAlignmentFromUsedBlock) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 128; |
| constexpr size_t kSplit2 = 512; |
| constexpr size_t kAlign = 32; |
| |
| std::array<std::byte, kN> bytes{}; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| Layout layout1(kSplit1, kAlign); |
| EXPECT_EQ(BlockType::AllocFirst(block1, layout1), pw::OkStatus()); |
| |
| Layout layout2(kSplit2, kAlign * 2); |
| BlockType* block2 = block1->Next(); |
| EXPECT_EQ(BlockType::AllocFirst(block2, layout2), pw::OkStatus()); |
| |
| EXPECT_EQ(block1->Alignment(), kAlign); |
| EXPECT_EQ(block2->Alignment(), kAlign * 2); |
| } |
| |
| TEST_FOR_EACH_BLOCK_TYPE(FreeBlockAlignmentIsAlwaysOne) { |
| constexpr size_t kN = 1024; |
| constexpr size_t kSplit1 = 128; |
| constexpr size_t kAlign = 32; |
| |
| std::array<std::byte, kN> bytes{}; |
| auto result = BlockType::Init(bytes); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| BlockType* block1 = *result; |
| |
| Layout layout(kSplit1, kAlign); |
| EXPECT_EQ(BlockType::AllocFirst(block1, layout), pw::OkStatus()); |
| block1->MarkFree(); |
| EXPECT_EQ(block1->Alignment(), 1U); |
| } |
| |
| } // namespace |