blob: f882d523b5c48ca04d81eb6289ea96c2b71f39e1 [file] [log] [blame]
// 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_allocator/block_testing.h"
#include "pw_assert/check.h"
#include "pw_assert/internal/check_impl.h"
#include "pw_span/span.h"
#include "pw_unit_test/framework.h"
namespace {
// Test fixtures.
using ::pw::allocator::Layout;
using ::pw::allocator::test::Preallocate;
using ::pw::allocator::test::Preallocation;
// The size of the memory region used in tests.
constexpr size_t kN = 1024;
// The large alignment used in alignment-related tests.
constexpr size_t kAlign = 64;
template <typename BlockType>
class BlockTest : public ::testing::Test {
protected:
// Some tests below need a block with a nonzero inner size to fit within
// alignment boundaries.
static_assert(kAlign > BlockType::kBlockOverhead + BlockType::kAlignment);
alignas(BlockType::kAlignment) std::array<std::byte, kN> bytes_;
};
// A block type with more overhead.
using LargeOffsetBlock = ::pw::allocator::Block<uint64_t>;
class LargeOffsetBlockTest : public BlockTest<LargeOffsetBlock> {
protected:
using BlockType = LargeOffsetBlock;
};
// A block type with less overhead.
using SmallOffsetBlock = ::pw::allocator::Block<uint16_t>;
class SmallOffsetBlockTest : public BlockTest<SmallOffsetBlock> {
protected:
using BlockType = SmallOffsetBlock;
};
// A block type with moderate overhead and support for poisoning.
using PoisonedBlock = ::pw::allocator::Block<uint32_t, alignof(uint32_t), true>;
class PoisonedBlockTest : public BlockTest<PoisonedBlock> {
protected:
using BlockType = PoisonedBlock;
};
/// Returns the smallest offset into the given memory region which can be
/// preceded by a valid block, and at which a block would have properly aligned
/// usable space of the given size.
///
/// @pre ``bytes`` must not be smaller than the calculated offset plus
/// ``layout.size()``.
template <typename BlockType>
size_t GetFirstAlignedOffset(pw::ByteSpan bytes, Layout layout) {
auto start = reinterpret_cast<uintptr_t>(bytes.data());
uintptr_t end = start + bytes.size();
size_t alignment = std::max(BlockType::kAlignment, layout.alignment());
// Find the minimum address of the usable space of the second block.
size_t min_addr = start;
PW_CHECK_ADD(min_addr, BlockType::kBlockOverhead + 1, &min_addr);
PW_CHECK_ADD(min_addr, BlockType::kBlockOverhead, &min_addr);
// Align the usable space and ensure it fits.
uintptr_t addr = pw::AlignUp(min_addr, alignment);
PW_CHECK_UINT_LE(addr + layout.size(), end);
// Return the offset to the start of the block.
PW_CHECK_SUB(addr, BlockType::kBlockOverhead, &addr);
return addr - start;
}
/// Returns the largest offset into the given memory region at which a block
/// would have properly aligned usable space of the given size.
///
/// @pre ``bytes`` must not be smaller than the calculated offset plus
/// ``layout.size()``.
template <typename BlockType>
size_t GetLastAlignedOffset(pw::ByteSpan bytes, Layout layout) {
auto start = reinterpret_cast<uintptr_t>(bytes.data());
uintptr_t end = start + bytes.size();
size_t alignment = std::max(BlockType::kAlignment, layout.alignment());
// Find the minimum address of the usable space of the second block.
size_t min_addr = start;
PW_CHECK_ADD(min_addr, BlockType::kBlockOverhead + 1, &min_addr);
PW_CHECK_ADD(min_addr, BlockType::kBlockOverhead, &min_addr);
// Align the usable space and ensure it fits.
uintptr_t addr;
PW_CHECK_SUB(end, layout.size(), &addr);
addr = pw::AlignDown(addr, alignment);
PW_CHECK_UINT_LE(min_addr, addr);
// Return the offset to the start of the block.
PW_CHECK_SUB(addr, BlockType::kBlockOverhead, &addr);
return addr - start;
}
// 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(pw::ByteSpan bytes_); \
TEST_F(LargeOffsetBlockTest, TestCase) { \
TestCase<LargeOffsetBlock>(bytes_); \
} \
TEST_F(SmallOffsetBlockTest, TestCase) { \
TestCase<SmallOffsetBlock>(bytes_); \
} \
TEST_F(PoisonedBlockTest, TestCase) { TestCase<PoisonedBlock>(bytes_); } \
template <typename BlockType> \
void TestCase([[maybe_unused]] pw::ByteSpan bytes_)
// Unit tests.
TEST_FOR_EACH_BLOCK_TYPE(CanCreateSingleAlignedBlock) {
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) {
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) {
std::array<std::byte, kN> bytes;
auto result = pw::allocator::Block<uint8_t>::Init(bytes);
EXPECT_EQ(result.status(), pw::Status::OutOfRange());
}
TEST_FOR_EACH_BLOCK_TYPE(CanAllocFirst) {
constexpr Layout kLayout(256, kAlign);
// Make sure the block's usable space is aligned.
size_t outer_size = GetFirstAlignedOffset<BlockType>(bytes_, kLayout);
auto* block = Preallocate<BlockType>(
bytes_,
{
{outer_size, Preallocation::kUsed},
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
block = block->Next();
// Allocate from the front of the block.
BlockType* prev = block->Prev();
EXPECT_EQ(BlockType::AllocFirst(block, kLayout), pw::OkStatus());
EXPECT_EQ(block->InnerSize(), kLayout.size());
auto 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(CanAllocFirstWithNewPrevBlock) {
constexpr Layout kLayout(256, kAlign);
// Make sure the block's usable space is not aligned.
size_t outer_size = GetFirstAlignedOffset<BlockType>(bytes_, kLayout);
outer_size += BlockType::kAlignment;
auto* block = Preallocate<BlockType>(
bytes_,
{
{outer_size, Preallocation::kUsed},
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
block = block->Next();
BlockType* prev = block->Prev();
// Allocate from the front of the block.
EXPECT_EQ(BlockType::AllocFirst(block, kLayout), pw::OkStatus());
EXPECT_EQ(block->InnerSize(), kLayout.size());
auto addr = reinterpret_cast<uintptr_t>(block->UsableSpace());
EXPECT_EQ(addr % kAlign, 0U);
EXPECT_TRUE(block->Used());
// A new free block was added.
EXPECT_LT(prev, block->Prev());
EXPECT_FALSE(block->Prev()->Used());
// Extra was split from the end of the block.
EXPECT_FALSE(block->Last());
}
TEST_FOR_EACH_BLOCK_TYPE(CanAllocFirstWithNoNewNextBlock) {
constexpr Layout kLayout(256, kAlign);
constexpr size_t kOuterSize = BlockType::kBlockOverhead + kLayout.size();
// Make sure the block's usable space is aligned.
size_t outer_size = GetFirstAlignedOffset<BlockType>(bytes_, kLayout);
auto* block = Preallocate<BlockType>(
bytes_,
{
{outer_size, Preallocation::kUsed},
{kOuterSize, Preallocation::kFree},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
block = block->Next();
BlockType* next = block->Next();
EXPECT_EQ(BlockType::AllocFirst(block, kLayout), pw::OkStatus());
EXPECT_EQ(block->InnerSize(), kLayout.size());
auto addr = reinterpret_cast<uintptr_t>(block->UsableSpace());
EXPECT_EQ(addr % kAlign, 0U);
EXPECT_TRUE(block->Used());
// No new trailing block was created.
EXPECT_EQ(next, block->Next());
}
TEST_FOR_EACH_BLOCK_TYPE(CanAllocFirstWithResizedPrevBlock) {
constexpr Layout kLayout(256, kAlign);
// Make sure the block's usable space is not aligned.
size_t outer_size = GetFirstAlignedOffset<BlockType>(bytes_, kLayout);
outer_size +=
pw::AlignUp(BlockType::kBlockOverhead, kAlign) - BlockType::kAlignment;
auto* block = Preallocate<BlockType>(
bytes_,
{
{outer_size, Preallocation::kUsed},
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
block = block->Next();
BlockType* prev = block->Prev();
size_t prev_inner_size = prev->InnerSize();
// Allocate from the front of the block.
EXPECT_EQ(BlockType::AllocFirst(block, kLayout), pw::OkStatus());
EXPECT_EQ(block->InnerSize(), kLayout.size());
auto addr = reinterpret_cast<uintptr_t>(block->UsableSpace());
EXPECT_EQ(addr % kAlign, 0U);
EXPECT_TRUE(block->Used());
/// Less than a minimum block was added to the previous block.
EXPECT_EQ(prev, block->Prev());
EXPECT_EQ(prev->InnerSize() - prev_inner_size, BlockType::kAlignment);
// Extra was split from the end of the block.
EXPECT_FALSE(block->Last());
}
TEST_FOR_EACH_BLOCK_TYPE(CannotAllocFirstIfTooSmallForAlignment) {
constexpr Layout kLayout(256, kAlign);
constexpr size_t kOuterSize = BlockType::kBlockOverhead + kLayout.size();
// Make sure the block's usable space is not aligned.
size_t outer_size = GetFirstAlignedOffset<BlockType>(bytes_, kLayout) + 1;
auto* block = Preallocate<BlockType>(
bytes_,
{
{outer_size, Preallocation::kUsed},
{kOuterSize, Preallocation::kFree},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
block = block->Next();
// Cannot allocate without room to a split a block for alignment.
EXPECT_EQ(BlockType::AllocFirst(block, kLayout), 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(CannotAllocFirstZeroSize) {
auto* block = Preallocate<BlockType>(
bytes_,
{
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
Layout layout(0, 1);
EXPECT_EQ(BlockType::AllocFirst(block, layout),
pw::Status::InvalidArgument());
}
TEST_FOR_EACH_BLOCK_TYPE(CannotAllocFirstFromUsed) {
auto* block = Preallocate<BlockType>(
bytes_,
{
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
Layout layout(1, 1);
EXPECT_EQ(BlockType::AllocFirst(block, layout),
pw::Status::FailedPrecondition());
}
TEST_FOR_EACH_BLOCK_TYPE(CanAllocLastWithNewPrevBlock) {
constexpr Layout kLayout(256, kAlign);
auto* block = Preallocate<BlockType>(
bytes_,
{
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
// Allocate from the back of the block.
EXPECT_EQ(BlockType::AllocLast(block, kLayout), pw::OkStatus());
EXPECT_GE(block->InnerSize(), kLayout.size());
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(CanAllocLastWithResizedPrevBlock) {
constexpr Layout kLayout(256, kAlign);
// Make sure the block's usable space is not aligned.
size_t outer_size = GetLastAlignedOffset<BlockType>(bytes_, kLayout);
outer_size -= BlockType::kAlignment;
auto* block = Preallocate<BlockType>(
bytes_,
{
{outer_size, Preallocation::kUsed},
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
block = block->Next();
BlockType* next = block->Next();
BlockType* prev = block->Prev();
size_t prev_inner_size = prev->InnerSize();
// Allocate from the back of the block.
EXPECT_EQ(BlockType::AllocLast(block, kLayout), pw::OkStatus());
EXPECT_GE(block->InnerSize(), kLayout.size());
auto addr = reinterpret_cast<uintptr_t>(block->UsableSpace());
EXPECT_EQ(addr % kAlign, 0U);
EXPECT_TRUE(block->Used());
/// Less than a minimum block was added to the previous block.
EXPECT_EQ(prev, block->Prev());
EXPECT_EQ(prev->InnerSize() - prev_inner_size, BlockType::kAlignment);
// No new trailing block was created.
EXPECT_EQ(next, block->Next());
}
TEST_FOR_EACH_BLOCK_TYPE(CannotAllocLastIfTooSmallForAlignment) {
constexpr Layout kLayout(256, kAlign);
constexpr size_t kOuterSize = BlockType::kBlockOverhead + kLayout.size();
// Make sure the block's usable space is not aligned.
size_t outer_size = GetFirstAlignedOffset<BlockType>(bytes_, kLayout) + 1;
auto* block = Preallocate<BlockType>(
bytes_,
{
{outer_size, Preallocation::kUsed},
{kOuterSize, Preallocation::kFree},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
block = block->Next();
// Cannot allocate without room to a split a block for alignment.
EXPECT_EQ(BlockType::AllocLast(block, kLayout),
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(CannotAllocLastZeroSize) {
auto* block = Preallocate<BlockType>(
bytes_,
{
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
Layout layout(0, 1);
EXPECT_EQ(BlockType::AllocLast(block, layout), pw::Status::InvalidArgument());
}
TEST_FOR_EACH_BLOCK_TYPE(CannotAllocLastFromUsed) {
auto* block = Preallocate<BlockType>(
bytes_,
{
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
Layout layout(1, 1);
EXPECT_EQ(BlockType::AllocLast(block, layout),
pw::Status::FailedPrecondition());
}
TEST_FOR_EACH_BLOCK_TYPE(FreeingNullDoesNothing) {
BlockType* block = nullptr;
BlockType::Free(block);
}
TEST_FOR_EACH_BLOCK_TYPE(FreeingFreeBlockDoesNothing) {
auto* block = Preallocate<BlockType>(
bytes_,
{
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
BlockType::Free(block);
}
TEST_FOR_EACH_BLOCK_TYPE(CanFree) {
auto* block = Preallocate<BlockType>(
bytes_,
{
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
BlockType::Free(block);
EXPECT_FALSE(block->Used());
EXPECT_EQ(block->OuterSize(), kN);
}
TEST_FOR_EACH_BLOCK_TYPE(CanFreeBlockWithoutMerging) {
constexpr size_t kOuterSize = 256;
auto* block = Preallocate<BlockType>(
bytes_,
{
{kOuterSize, Preallocation::kUsed},
{kOuterSize, Preallocation::kUsed},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
block = block->Next();
BlockType* next = block->Next();
BlockType* prev = block->Prev();
BlockType::Free(block);
EXPECT_FALSE(block->Used());
EXPECT_EQ(next, block->Next());
EXPECT_EQ(prev, block->Prev());
}
TEST_FOR_EACH_BLOCK_TYPE(CanFreeBlockAndMergeWithPrev) {
constexpr size_t kOuterSize = 256;
auto* block = Preallocate<BlockType>(
bytes_,
{
{kOuterSize, Preallocation::kFree},
{kOuterSize, Preallocation::kUsed},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
block = block->Next();
BlockType* next = block->Next();
BlockType::Free(block);
EXPECT_FALSE(block->Used());
EXPECT_EQ(block->Prev(), nullptr);
EXPECT_EQ(block->Next(), next);
}
TEST_FOR_EACH_BLOCK_TYPE(CanFreeBlockAndMergeWithNext) {
constexpr size_t kOuterSize = 256;
auto* block = Preallocate<BlockType>(
bytes_,
{
{kOuterSize, Preallocation::kUsed},
{kOuterSize, Preallocation::kUsed},
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
block = block->Next();
BlockType* prev = block->Prev();
BlockType::Free(block);
EXPECT_FALSE(block->Used());
EXPECT_EQ(block->Prev(), prev);
EXPECT_TRUE(block->Last());
}
TEST_FOR_EACH_BLOCK_TYPE(CanFreeBlockAndMergeWithBoth) {
constexpr size_t kOuterSize = 256;
auto* block = Preallocate<BlockType>(
bytes_,
{
{kOuterSize, Preallocation::kFree},
{kOuterSize, Preallocation::kUsed},
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
block = block->Next();
BlockType::Free(block);
EXPECT_FALSE(block->Used());
EXPECT_EQ(block->Prev(), nullptr);
EXPECT_TRUE(block->Last());
}
TEST_FOR_EACH_BLOCK_TYPE(CanResizeBlockSameSize) {
constexpr size_t kOuterSize = 256;
auto* block = Preallocate<BlockType>(
bytes_,
{
{kOuterSize, Preallocation::kUsed},
{kOuterSize, Preallocation::kUsed},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
block = block->Next();
EXPECT_EQ(BlockType::Resize(block, block->InnerSize()), pw::OkStatus());
}
TEST_FOR_EACH_BLOCK_TYPE(CannotResizeFreeBlock) {
constexpr size_t kOuterSize = 256;
auto* block = Preallocate<BlockType>(
bytes_,
{
{kOuterSize, Preallocation::kUsed},
{kOuterSize, Preallocation::kFree},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
block = block->Next();
EXPECT_EQ(BlockType::Resize(block, block->InnerSize()),
pw::Status::FailedPrecondition());
}
TEST_FOR_EACH_BLOCK_TYPE(CanResizeBlockSmallerWithNextFree) {
constexpr size_t kOuterSize = 256;
auto* block = Preallocate<BlockType>(
bytes_,
{
{kOuterSize, Preallocation::kUsed},
{kOuterSize, Preallocation::kUsed},
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
block = block->Next();
size_t next_inner_size = block->Next()->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 = block->InnerSize() - delta;
EXPECT_EQ(BlockType::Resize(block, new_inner_size), pw::OkStatus());
EXPECT_EQ(block->InnerSize(), new_inner_size);
BlockType* next = block->Next();
EXPECT_FALSE(next->Used());
EXPECT_EQ(next->InnerSize(), next_inner_size + delta);
}
TEST_FOR_EACH_BLOCK_TYPE(CanResizeBlockLargerWithNextFree) {
constexpr size_t kOuterSize = 256;
auto* block = Preallocate<BlockType>(
bytes_,
{
{kOuterSize, Preallocation::kUsed},
{kOuterSize, Preallocation::kFree},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
size_t next_inner_size = block->Next()->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;
size_t new_inner_size = block->InnerSize() + delta;
EXPECT_EQ(BlockType::Resize(block, new_inner_size), pw::OkStatus());
EXPECT_EQ(block->InnerSize(), new_inner_size);
BlockType* next = block->Next();
EXPECT_FALSE(next->Used());
EXPECT_EQ(next->InnerSize(), next_inner_size - delta);
}
TEST_FOR_EACH_BLOCK_TYPE(CannotResizeBlockMuchLargerWithNextFree) {
constexpr size_t kOuterSize = 256;
auto* block = Preallocate<BlockType>(
bytes_,
{
{kOuterSize, Preallocation::kUsed},
{kOuterSize, Preallocation::kFree},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
size_t new_inner_size = block->InnerSize() + kOuterSize + 1;
EXPECT_EQ(BlockType::Resize(block, new_inner_size), pw::Status::OutOfRange());
}
TEST_FOR_EACH_BLOCK_TYPE(CanResizeBlockSmallerWithNextUsed) {
constexpr Layout kLayout(256, kAlign);
constexpr size_t kOuterSize = BlockType::kBlockOverhead + kLayout.size();
auto* block = Preallocate<BlockType>(
bytes_,
{
{kOuterSize, Preallocation::kUsed},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
// Shrink the block.
size_t delta = kLayout.size() / 2;
size_t new_inner_size = block->InnerSize() - delta;
EXPECT_EQ(BlockType::Resize(block, new_inner_size), pw::OkStatus());
EXPECT_EQ(block->InnerSize(), new_inner_size);
BlockType* next = block->Next();
EXPECT_FALSE(next->Used());
EXPECT_EQ(next->OuterSize(), delta);
}
TEST_FOR_EACH_BLOCK_TYPE(CannotResizeBlockLargerWithNextUsed) {
constexpr size_t kOuterSize = 256;
auto* block = Preallocate<BlockType>(
bytes_,
{
{kOuterSize, Preallocation::kUsed},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
size_t delta = BlockType::kBlockOverhead / 2;
size_t new_inner_size = block->InnerSize() + delta;
EXPECT_EQ(BlockType::Resize(block, new_inner_size), pw::Status::OutOfRange());
}
TEST_FOR_EACH_BLOCK_TYPE(CannotResizeFromNull) {
BlockType* block = nullptr;
EXPECT_EQ(BlockType::Resize(block, 1), pw::Status::InvalidArgument());
}
TEST_FOR_EACH_BLOCK_TYPE(CanCheckValidBlock) {
constexpr size_t kOuterSize1 = 512;
constexpr size_t kOuterSize2 = 256;
auto* block = Preallocate<BlockType>(
bytes_,
{
{kOuterSize1, Preallocation::kUsed},
{kOuterSize2, Preallocation::kFree},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
EXPECT_TRUE(block->IsValid());
block->CrashIfInvalid();
block = block->Next();
EXPECT_TRUE(block->IsValid());
block->CrashIfInvalid();
block = block->Next();
EXPECT_TRUE(block->IsValid());
block->CrashIfInvalid();
}
TEST_FOR_EACH_BLOCK_TYPE(CanCheckInvalidBlock) {
constexpr size_t kOuterSize1 = 128;
constexpr size_t kOuterSize2 = 384;
auto* block1 = Preallocate<BlockType>(
bytes_,
{
{kOuterSize1, Preallocation::kUsed},
{kOuterSize2, Preallocation::kFree},
{Preallocation::kSizeRemaining, Preallocation::kUsed},
});
BlockType* block2 = block1->Next();
BlockType* block3 = block2->Next();
// 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_F(PoisonedBlockTest, CanCheckPoison) {
auto* block = Preallocate<BlockType>(
bytes_,
{
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
// 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) {
auto* block1 = Preallocate<BlockType>(
bytes_,
{
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
void* ptr = block1->UsableSpace();
BlockType* block2 = BlockType::FromUsableSpace(ptr);
EXPECT_EQ(block1, block2);
}
TEST_FOR_EACH_BLOCK_TYPE(CanGetConstBlockFromUsableSpace) {
const auto* block1 = Preallocate<BlockType>(
bytes_,
{
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
const void* ptr = block1->UsableSpace();
const BlockType* block2 = BlockType::FromUsableSpace(ptr);
EXPECT_EQ(block1, block2);
}
TEST_FOR_EACH_BLOCK_TYPE(CanGetAlignmentFromUsedBlock) {
constexpr Layout kLayout1(128, kAlign);
constexpr Layout kLayout2(384, kAlign * 2);
auto* block1 = Preallocate<BlockType>(
bytes_,
{
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
EXPECT_EQ(BlockType::AllocFirst(block1, kLayout1), pw::OkStatus());
BlockType* block2 = block1->Next();
EXPECT_EQ(BlockType::AllocFirst(block2, kLayout2), pw::OkStatus());
EXPECT_EQ(block1->Alignment(), kAlign);
EXPECT_EQ(block2->Alignment(), kAlign * 2);
}
TEST_FOR_EACH_BLOCK_TYPE(FreeBlockAlignmentIsAlwaysOne) {
constexpr Layout kLayout1(128, kAlign);
constexpr Layout kLayout2(384, kAlign * 2);
auto* block1 = Preallocate<BlockType>(
bytes_,
{
{Preallocation::kSizeRemaining, Preallocation::kFree},
});
EXPECT_EQ(BlockType::AllocFirst(block1, kLayout1), pw::OkStatus());
BlockType* block2 = block1->Next();
EXPECT_EQ(BlockType::AllocFirst(block2, kLayout2), pw::OkStatus());
EXPECT_EQ(block1->Alignment(), kAlign);
BlockType::Free(block1);
EXPECT_EQ(block1->Alignment(), 1U);
}
} // namespace