diff options
author | aarongreen <aarongreen@google.com> | 2023-11-08 19:21:36 +0000 |
---|---|---|
committer | CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2023-11-08 19:21:36 +0000 |
commit | 74ed509bc15e65bc898284b697cacde6684c8288 (patch) | |
tree | d5d96f74042fc0385245f98ea2333281bb75d8d7 | |
parent | 87636efd044bee24f360c979153abc7df28a7690 (diff) | |
download | pigweed-74ed509bc15e65bc898284b697cacde6684c8288.tar.gz |
pw_allocator: Refactor test support and example allocator
This CL does the following:
* It move the SimpleAllocator previously described in Block's
class-level comments to an actual source file, and adds a test for
it. The class is now included directly into the module docs.
* It refactors the FakeAllocator to use SimpleAllocator and renames it
to AllocatorForTest. It also moves this from pw_allocator_private/ to
public/pw_allocator/, as these utilities may be useful for testing
custom allocators provided by downstream projects.
* It updates the callers of FakeAllocator to use AllocatorForTest.
Bug: b/306686936
Change-Id: I41953e3b64fa831bd23418496ffa683917944b3f
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/177653
Commit-Queue: Aaron Green <aarongreen@google.com>
Reviewed-by: Taylor Cramer <cramertj@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
-rw-r--r-- | pw_allocator/BUILD.bazel | 56 | ||||
-rw-r--r-- | pw_allocator/BUILD.gn | 33 | ||||
-rw-r--r-- | pw_allocator/CMakeLists.txt | 59 | ||||
-rw-r--r-- | pw_allocator/allocator_metric_proxy_test.cc | 20 | ||||
-rw-r--r-- | pw_allocator/allocator_test.cc | 15 | ||||
-rw-r--r-- | pw_allocator/allocator_testing.cc | 110 | ||||
-rw-r--r-- | pw_allocator/docs.rst | 13 | ||||
-rw-r--r-- | pw_allocator/fallback_allocator_test.cc | 148 | ||||
-rw-r--r-- | pw_allocator/libc_allocator_test.cc | 3 | ||||
-rw-r--r-- | pw_allocator/public/pw_allocator/allocator_testing.h | 134 | ||||
-rw-r--r-- | pw_allocator/public/pw_allocator/block.h | 49 | ||||
-rw-r--r-- | pw_allocator/public/pw_allocator/simple_allocator.h | 87 | ||||
-rw-r--r-- | pw_allocator/pw_allocator_private/allocator_testing.h | 78 | ||||
-rw-r--r-- | pw_allocator/simple_allocator_test.cc | 66 | ||||
-rw-r--r-- | pw_allocator/split_free_list_allocator_test.cc | 158 | ||||
-rw-r--r-- | pw_allocator/unique_ptr_test.cc | 40 |
16 files changed, 645 insertions, 424 deletions
diff --git a/pw_allocator/BUILD.bazel b/pw_allocator/BUILD.bazel index 13c82dbbc..c7c7d9ae3 100644 --- a/pw_allocator/BUILD.bazel +++ b/pw_allocator/BUILD.bazel @@ -121,49 +121,62 @@ pw_cc_library( ) pw_cc_library( - name = "split_free_list_allocator", + name = "libc_allocator", srcs = [ - "split_free_list_allocator.cc", + "libc_allocator.cc", ], hdrs = [ - "public/pw_allocator/split_free_list_allocator.h", + "public/pw_allocator/libc_allocator.h", ], includes = ["public"], deps = [ ":allocator", - ":block", "//pw_assert", "//pw_bytes", - "//pw_result", "//pw_status", ], ) pw_cc_library( - name = "libc_allocator", - srcs = [ - "libc_allocator.cc", + name = "null_allocator", + hdrs = [ + "public/pw_allocator/null_allocator.h", + ], + includes = ["public"], + deps = [ + ":allocator", ], +) + +pw_cc_library( + name = "simple_allocator", hdrs = [ - "public/pw_allocator/libc_allocator.h", + "public/pw_allocator/simple_allocator.h", ], includes = ["public"], deps = [ ":allocator", - "//pw_assert", + ":block", "//pw_bytes", - "//pw_status", ], ) pw_cc_library( - name = "null_allocator", + name = "split_free_list_allocator", + srcs = [ + "split_free_list_allocator.cc", + ], hdrs = [ - "public/pw_allocator/null_allocator.h", + "public/pw_allocator/split_free_list_allocator.h", ], includes = ["public"], deps = [ ":allocator", + ":block", + "//pw_assert", + "//pw_bytes", + "//pw_result", + "//pw_status", ], ) @@ -173,13 +186,16 @@ pw_cc_library( "allocator_testing.cc", ], hdrs = [ - "pw_allocator_private/allocator_testing.h", + "public/pw_allocator/allocator_testing.h", ], + includes = ["public"], deps = [ ":allocator", ":block", + ":simple_allocator", "//pw_assert", "//pw_bytes", + "//pw_unit_test", ], ) @@ -279,11 +295,23 @@ pw_cc_test( ) pw_cc_test( + name = "simple_allocator_test", + srcs = [ + "simple_allocator_test.cc", + ], + deps = [ + ":allocator_testing", + ":simple_allocator", + ], +) + +pw_cc_test( name = "split_free_list_allocator_test", srcs = [ "split_free_list_allocator_test.cc", ], deps = [ + ":allocator_testing", ":split_free_list_allocator", "//pw_bytes", "//pw_containers:vector", diff --git a/pw_allocator/BUILD.gn b/pw_allocator/BUILD.gn index f1f2ac7c7..e7039f0a1 100644 --- a/pw_allocator/BUILD.gn +++ b/pw_allocator/BUILD.gn @@ -136,6 +136,16 @@ pw_source_set("null_allocator") { public_deps = [ ":allocator" ] } +pw_source_set("simple_allocator") { + public_configs = [ ":default_config" ] + public = [ "public/pw_allocator/simple_allocator.h" ] + public_deps = [ + ":allocator", + ":block", + dir_pw_bytes, + ] +} + pw_source_set("split_free_list_allocator") { public_configs = [ ":default_config" ] public = [ "public/pw_allocator/split_free_list_allocator.h" ] @@ -181,19 +191,22 @@ pw_test_group("tests") { ":freelist_heap_test", ":libc_allocator_test", ":null_allocator_test", + ":simple_allocator_test", ":split_free_list_allocator_test", ":unique_ptr_test", ] } pw_source_set("allocator_testing") { - public = [ "pw_allocator_private/allocator_testing.h" ] + public = [ "public/pw_allocator/allocator_testing.h" ] public_deps = [ ":allocator", ":block", - dir_pw_assert, + ":simple_allocator", dir_pw_bytes, + dir_pw_unit_test, ] + deps = [ dir_pw_assert ] sources = [ "allocator_testing.cc" ] } @@ -258,8 +271,19 @@ pw_test("null_allocator_test") { sources = [ "null_allocator_test.cc" ] } +pw_test("simple_allocator_test") { + configs = [ ":enable_heap_poison" ] + deps = [ + ":allocator_testing", + ":simple_allocator", + ] + sources = [ "simple_allocator_test.cc" ] +} + pw_test("split_free_list_allocator_test") { + configs = [ ":enable_heap_poison" ] deps = [ + ":allocator_testing", ":split_free_list_allocator", "$dir_pw_containers:vector", dir_pw_bytes, @@ -273,7 +297,10 @@ pw_test("unique_ptr_test") { } pw_doc_group("docs") { - inputs = [ "doc_resources/pw_allocator_heap_visualizer_demo.png" ] + inputs = [ + "doc_resources/pw_allocator_heap_visualizer_demo.png", + "public/pw_allocator/simple_allocator.h", + ] sources = [ "docs.rst" ] report_deps = [ ":allocator_size_report" ] } diff --git a/pw_allocator/CMakeLists.txt b/pw_allocator/CMakeLists.txt index 82ed74540..6f923edc7 100644 --- a/pw_allocator/CMakeLists.txt +++ b/pw_allocator/CMakeLists.txt @@ -102,55 +102,69 @@ pw_add_library(pw_allocator.freelist_heap STATIC freelist_heap.cc ) -pw_add_library(pw_allocator.split_free_list_allocator STATIC +pw_add_library(pw_allocator.libc_allocator STATIC + SOURCES + libc_allocator.cc HEADERS - public/pw_allocator/split_free_list_allocator.h + public/pw_allocator/libc_allocator.h PUBLIC_INCLUDES public PUBLIC_DEPS pw_allocator.allocator - pw_allocator.block - pw_bytes - pw_result pw_status PRIVATE_DEPS pw_assert - SOURCES - split_free_list_allocator.cc + pw_bytes ) -pw_add_library(pw_allocator.libc_allocator STATIC - SOURCES - libc_allocator.cc +pw_add_library(pw_allocator.null_allocator INTERFACE HEADERS - public/pw_allocator/libc_allocator.h + public/pw_allocator/null_allocator.h PUBLIC_INCLUDES public PUBLIC_DEPS pw_allocator.allocator - pw_status - PRIVATE_DEPS - pw_assert +) + +pw_add_library(pw_allocator.simple_allocator INTERFACE + HEADERS + public/pw_allocator/simple_allocator.h + PUBLIC_DEPS + pw_allocator.allocator + pw_allocator.block pw_bytes ) -pw_add_library(pw_allocator.null_allocator INTERFACE +pw_add_library(pw_allocator.split_free_list_allocator STATIC HEADERS - public/pw_allocator/null_allocator.h + public/pw_allocator/split_free_list_allocator.h PUBLIC_INCLUDES public PUBLIC_DEPS pw_allocator.allocator + pw_allocator.block + pw_bytes + pw_result + pw_status + PRIVATE_DEPS + pw_assert + SOURCES + split_free_list_allocator.cc ) pw_add_library(pw_allocator.allocator_testing STATIC HEADERS - pw_allocator_private/allocator_testing.h + public/pw_allocator/allocator_testing.h + PUBLIC_INCLUDES + public PUBLIC_DEPS pw_allocator.allocator pw_allocator.block - pw_assert + pw_allocator.simple_allocator pw_bytes + PRIVATE_DEPS + pw_assert + pw_unit_test SOURCES allocator_testing.cc ) @@ -245,10 +259,19 @@ pw_add_test(pw_allocator.null_allocator_test pw_allocator ) +pw_add_test(pw_allocator.simple_allocator_test + SOURCES + simple_allocator_test.cc + PRIVATE_DEPS + pw_allocator.allocator_testing + pw_allocator.simple_allocator +) + pw_add_test(pw_allocator.split_free_list_allocator_test SOURCES split_free_list_allocator_test.cc PRIVATE_DEPS + pw_allocator.allocator_testing pw_allocator.split_free_list_allocator pw_containers.vector pw_bytes diff --git a/pw_allocator/allocator_metric_proxy_test.cc b/pw_allocator/allocator_metric_proxy_test.cc index e426349a9..cfc94b2f0 100644 --- a/pw_allocator/allocator_metric_proxy_test.cc +++ b/pw_allocator/allocator_metric_proxy_test.cc @@ -15,27 +15,23 @@ #include "pw_allocator/allocator_metric_proxy.h" #include "gtest/gtest.h" -#include "pw_allocator_private/allocator_testing.h" +#include "pw_allocator/allocator_testing.h" namespace pw::allocator { namespace { // Test fixtures. -struct AllocatorMetricProxyTest : ::testing::Test { - private: - std::array<std::byte, 256> buffer = {}; - test::FakeAllocator wrapped; +class AllocatorMetricProxyTest : public ::testing::Test { + protected: + AllocatorMetricProxyTest() : allocator(0) {} - public: - AllocatorMetricProxy allocator; + void SetUp() override { allocator.Initialize(*wrapped); } - AllocatorMetricProxyTest() : allocator(0) {} + AllocatorMetricProxy allocator; - void SetUp() override { - EXPECT_TRUE(wrapped.Initialize(buffer).ok()); - allocator.Initialize(wrapped); - } + private: + test::AllocatorForTestWithBuffer<256> wrapped; }; // Unit tests. diff --git a/pw_allocator/allocator_test.cc b/pw_allocator/allocator_test.cc index bbd3f6c77..f0bd1981d 100644 --- a/pw_allocator/allocator_test.cc +++ b/pw_allocator/allocator_test.cc @@ -17,7 +17,7 @@ #include <cstddef> #include "gtest/gtest.h" -#include "pw_allocator_private/allocator_testing.h" +#include "pw_allocator/allocator_testing.h" #include "pw_bytes/alignment.h" namespace pw::allocator { @@ -25,14 +25,15 @@ namespace { // Test fixtures. -struct AllocatorTest : ::testing::Test { - private: - std::array<std::byte, 256> buffer = {}; +class AllocatorTest : public ::testing::Test { + protected: + void SetUp() override { EXPECT_EQ(allocator.Init(buffer), OkStatus()); } + void TearDown() override { allocator.DeallocateAll(); } - public: - test::FakeAllocator allocator; + test::AllocatorForTest allocator; - void SetUp() override { EXPECT_EQ(allocator.Initialize(buffer), OkStatus()); } + private: + std::array<std::byte, 256> buffer = {}; }; // Unit tests diff --git a/pw_allocator/allocator_testing.cc b/pw_allocator/allocator_testing.cc index bc67f330b..47d046aae 100644 --- a/pw_allocator/allocator_testing.cc +++ b/pw_allocator/allocator_testing.cc @@ -12,31 +12,35 @@ // License for the specific language governing permissions and limitations under // the License. -#include "pw_allocator_private/allocator_testing.h" +#include "pw_allocator/allocator_testing.h" #include "pw_assert/check.h" -#include "pw_bytes/alignment.h" namespace pw::allocator::test { -Status FakeAllocator::Initialize(ByteSpan buffer) { - auto result = BlockType::Init(buffer); - if (!result.ok()) { - return result.status(); +AllocatorForTest::~AllocatorForTest() { + for (auto* block : allocator_.blocks()) { + PW_DCHECK( + !block->Used(), + "The block at %p was still in use when its allocator was " + "destroyed. All memory allocated by an allocator must be released " + "before the allocator goes out of scope.", + static_cast<void*>(block)); } - begin_ = *result; - end_ = begin_->Next(); +} + +Status AllocatorForTest::Init(ByteSpan bytes) { ResetParameters(); - return OkStatus(); + return allocator_.Init(bytes); } -void FakeAllocator::Exhaust() { - for (BlockType* block = begin_; block != end_; block = block->Next()) { +void AllocatorForTest::Exhaust() { + for (auto* block : allocator_.blocks()) { block->MarkUsed(); } } -void FakeAllocator::ResetParameters() { +void AllocatorForTest::ResetParameters() { allocate_size_ = 0; deallocate_ptr_ = nullptr; deallocate_size_ = 0; @@ -45,82 +49,38 @@ void FakeAllocator::ResetParameters() { resize_new_size_ = 0; } -Status FakeAllocator::DoQuery(const void* ptr, size_t size, size_t) const { - PW_CHECK(begin_ != nullptr); - if (size == 0 || ptr == nullptr) { - return Status::OutOfRange(); - } - const auto* bytes = static_cast<const std::byte*>(ptr); - BlockType* target = BlockType::FromUsableSpace(const_cast<std::byte*>(bytes)); - if (!target->IsValid()) { - return Status::OutOfRange(); - } - size = AlignUp(size, BlockType::kAlignment); - for (BlockType* block = begin_; block != end_; block = block->Next()) { - if (block == target && block->InnerSize() == size) { - return OkStatus(); - } +void AllocatorForTest::DeallocateAll() { + for (auto* block : allocator_.blocks()) { + BlockType::Free(block); } - return Status::OutOfRange(); + ResetParameters(); +} + +Status AllocatorForTest::DoQuery(const void* ptr, + size_t size, + size_t alignment) const { + return allocator_.QueryUnchecked(ptr, size, alignment); } -void* FakeAllocator::DoAllocate(size_t size, size_t) { - PW_CHECK(begin_ != nullptr); +void* AllocatorForTest::DoAllocate(size_t size, size_t alignment) { allocate_size_ = size; - for (BlockType* block = begin_; block != end_; block = block->Next()) { - if (block->Used() || block->InnerSize() < size) { - continue; - } - Result<BlockType*> result = BlockType::Split(block, size); - if (result.ok()) { - BlockType* next = *result; - BlockType::MergeNext(next).IgnoreError(); - } - block->MarkUsed(); - return block->UsableSpace(); - } - return nullptr; + return allocator_.AllocateUnchecked(size, alignment); } -void FakeAllocator::DoDeallocate(void* ptr, size_t size, size_t alignment) { - PW_CHECK(begin_ != nullptr); +void AllocatorForTest::DoDeallocate(void* ptr, size_t size, size_t alignment) { deallocate_ptr_ = ptr; deallocate_size_ = size; - if (!DoQuery(ptr, size, alignment).ok()) { - return; - } - BlockType* block = BlockType::FromUsableSpace(static_cast<std::byte*>(ptr)); - block->MarkFree(); - BlockType::MergeNext(block).IgnoreError(); - BlockType* prev = block->Prev(); - BlockType::MergeNext(prev).IgnoreError(); + return allocator_.DeallocateUnchecked(ptr, size, alignment); } -bool FakeAllocator::DoResize(void* ptr, - size_t old_size, - size_t old_alignment, - size_t new_size) { - PW_CHECK(begin_ != nullptr); +bool AllocatorForTest::DoResize(void* ptr, + size_t old_size, + size_t old_alignment, + size_t new_size) { resize_ptr_ = ptr; resize_old_size_ = old_size; resize_new_size_ = new_size; - if (!DoQuery(ptr, old_size, old_alignment).ok() || old_size == 0 || - new_size == 0) { - return false; - } - if (old_size == new_size) { - return true; - } - BlockType* block = BlockType::FromUsableSpace(static_cast<std::byte*>(ptr)); - block->MarkFree(); - BlockType::MergeNext(block).IgnoreError(); - - Result<BlockType*> result = BlockType::Split(block, new_size); - if (!result.ok()) { - BlockType::Split(block, old_size).IgnoreError(); - } - block->MarkUsed(); - return block->InnerSize() >= new_size; + return allocator_.ResizeUnchecked(ptr, old_size, old_alignment, new_size); } } // namespace pw::allocator::test diff --git a/pw_allocator/docs.rst b/pw_allocator/docs.rst index decaf7e16..0d5545ccf 100644 --- a/pw_allocator/docs.rst +++ b/pw_allocator/docs.rst @@ -29,6 +29,19 @@ Allocator .. doxygenclass:: pw::allocator::Allocator :members: +Example +------- +As an example, the following implements a simple allocator that tracks memory +using ``Block``. + +.. literalinclude:: public/pw_allocator/simple_allocator.h + :language: cpp + :linenos: + :start-after: [pw_allocator_examples_simple_allocator] + :end-before: [pw_allocator_examples_simple_allocator] + +Other Implemetations +-------------------- Provided implementations of the ``Allocator`` interface include: - ``AllocatorMetricProxy``: Wraps another allocator and records its usage. diff --git a/pw_allocator/fallback_allocator_test.cc b/pw_allocator/fallback_allocator_test.cc index 982c6cc7d..c623fa652 100644 --- a/pw_allocator/fallback_allocator_test.cc +++ b/pw_allocator/fallback_allocator_test.cc @@ -15,7 +15,7 @@ #include "pw_allocator/fallback_allocator.h" #include "gtest/gtest.h" -#include "pw_allocator_private/allocator_testing.h" +#include "pw_allocator/allocator_testing.h" #include "pw_status/status.h" namespace pw::allocator { @@ -23,75 +23,73 @@ namespace { // Test fixtures. -struct FallbackAllocatorTest : ::testing::Test { - private: - std::array<std::byte, 128> buffer1 = {}; - std::array<std::byte, 128> buffer2 = {}; +class FallbackAllocatorTest : public ::testing::Test { + protected: + void SetUp() override { allocator.Initialize(*primary, *secondary); } - public: - test::FakeAllocator primary; - test::FakeAllocator secondary; - FallbackAllocator allocator; - - void SetUp() override { - EXPECT_EQ(primary.Initialize(buffer1), OkStatus()); - EXPECT_EQ(secondary.Initialize(buffer2), OkStatus()); - allocator.Initialize(primary, secondary); + void TearDown() override { + primary->DeallocateAll(); + secondary->DeallocateAll(); } + + test::AllocatorForTestWithBuffer<128> primary; + test::AllocatorForTestWithBuffer<128> secondary; + FallbackAllocator allocator; }; // Unit tests. TEST_F(FallbackAllocatorTest, QueryValidPrimary) { Layout layout = Layout::Of<uint32_t>(); - void* ptr = primary.Allocate(layout); - EXPECT_TRUE(primary.Query(ptr, layout).ok()); - EXPECT_EQ(secondary.Query(ptr, layout), Status::OutOfRange()); + void* ptr = primary->Allocate(layout); + EXPECT_TRUE(primary->Query(ptr, layout).ok()); + EXPECT_EQ(secondary->Query(ptr, layout), Status::OutOfRange()); EXPECT_TRUE(allocator.Query(ptr, layout).ok()); } TEST_F(FallbackAllocatorTest, QueryValidSecondary) { Layout layout = Layout::Of<uint32_t>(); - void* ptr = secondary.Allocate(layout); - EXPECT_FALSE(primary.Query(ptr, layout).ok()); - EXPECT_TRUE(secondary.Query(ptr, layout).ok()); + void* ptr = secondary->Allocate(layout); + EXPECT_FALSE(primary->Query(ptr, layout).ok()); + EXPECT_TRUE(secondary->Query(ptr, layout).ok()); EXPECT_TRUE(allocator.Query(ptr, layout).ok()); } TEST_F(FallbackAllocatorTest, QueryInvalidPtr) { std::array<std::byte, 128> buffer = {}; - test::FakeAllocator other; - ASSERT_TRUE(other.Initialize(buffer).ok()); + test::AllocatorForTest other; + ASSERT_EQ(other.Init(buffer), OkStatus()); Layout layout = Layout::Of<uint32_t>(); void* ptr = other.Allocate(layout); - EXPECT_FALSE(primary.Query(ptr, layout).ok()); - EXPECT_FALSE(secondary.Query(ptr, layout).ok()); + EXPECT_FALSE(primary->Query(ptr, layout).ok()); + EXPECT_FALSE(secondary->Query(ptr, layout).ok()); EXPECT_FALSE(allocator.Query(ptr, layout).ok()); + other.DeallocateAll(); } TEST_F(FallbackAllocatorTest, AllocateFromPrimary) { Layout layout = Layout::Of<uint32_t>(); void* ptr = allocator.Allocate(layout); EXPECT_NE(ptr, nullptr); - EXPECT_EQ(primary.allocate_size(), layout.size()); - EXPECT_EQ(secondary.allocate_size(), 0U); + EXPECT_EQ(primary->allocate_size(), layout.size()); + EXPECT_EQ(secondary->allocate_size(), 0U); } TEST_F(FallbackAllocatorTest, AllocateFromSecondary) { - primary.Exhaust(); + primary->Exhaust(); Layout layout = Layout::Of<uint32_t>(); void* ptr = allocator.Allocate(layout); EXPECT_NE(ptr, nullptr); - EXPECT_EQ(primary.allocate_size(), layout.size()); - EXPECT_EQ(secondary.allocate_size(), layout.size()); + EXPECT_EQ(primary->allocate_size(), layout.size()); + EXPECT_EQ(secondary->allocate_size(), layout.size()); } TEST_F(FallbackAllocatorTest, AllocateFailure) { Layout layout = Layout::Of<uint32_t[0x10000]>(); void* ptr = allocator.Allocate(layout); EXPECT_EQ(ptr, nullptr); - EXPECT_EQ(primary.allocate_size(), layout.size()); - EXPECT_EQ(secondary.allocate_size(), layout.size()); + EXPECT_EQ(primary->allocate_size(), layout.size()); + EXPECT_EQ(secondary->allocate_size(), layout.size()); } TEST_F(FallbackAllocatorTest, DeallocateUsingPrimary) { @@ -99,22 +97,22 @@ TEST_F(FallbackAllocatorTest, DeallocateUsingPrimary) { void* ptr = allocator.Allocate(layout); ASSERT_NE(ptr, nullptr); allocator.Deallocate(ptr, layout); - EXPECT_EQ(primary.deallocate_ptr(), ptr); - EXPECT_EQ(primary.deallocate_size(), layout.size()); - EXPECT_EQ(secondary.deallocate_ptr(), nullptr); - EXPECT_EQ(secondary.deallocate_size(), 0U); + EXPECT_EQ(primary->deallocate_ptr(), ptr); + EXPECT_EQ(primary->deallocate_size(), layout.size()); + EXPECT_EQ(secondary->deallocate_ptr(), nullptr); + EXPECT_EQ(secondary->deallocate_size(), 0U); } TEST_F(FallbackAllocatorTest, DeallocateUsingSecondary) { - primary.Exhaust(); + primary->Exhaust(); Layout layout = Layout::Of<uint32_t>(); void* ptr = allocator.Allocate(layout); ASSERT_NE(ptr, nullptr); allocator.Deallocate(ptr, layout); - EXPECT_EQ(primary.deallocate_ptr(), nullptr); - EXPECT_EQ(primary.deallocate_size(), 0U); - EXPECT_EQ(secondary.deallocate_ptr(), ptr); - EXPECT_EQ(secondary.deallocate_size(), layout.size()); + EXPECT_EQ(primary->deallocate_ptr(), nullptr); + EXPECT_EQ(primary->deallocate_size(), 0U); + EXPECT_EQ(secondary->deallocate_ptr(), ptr); + EXPECT_EQ(secondary->deallocate_size(), layout.size()); } TEST_F(FallbackAllocatorTest, ResizePrimary) { @@ -124,69 +122,69 @@ TEST_F(FallbackAllocatorTest, ResizePrimary) { size_t new_size = sizeof(uint32_t[3]); EXPECT_TRUE(allocator.Resize(ptr, old_layout, new_size)); - EXPECT_EQ(primary.resize_ptr(), ptr); - EXPECT_EQ(primary.resize_old_size(), old_layout.size()); - EXPECT_EQ(primary.resize_new_size(), new_size); + EXPECT_EQ(primary->resize_ptr(), ptr); + EXPECT_EQ(primary->resize_old_size(), old_layout.size()); + EXPECT_EQ(primary->resize_new_size(), new_size); // Secondary should not be touched. - EXPECT_EQ(secondary.resize_ptr(), nullptr); - EXPECT_EQ(secondary.resize_old_size(), 0U); - EXPECT_EQ(secondary.resize_new_size(), 0U); + EXPECT_EQ(secondary->resize_ptr(), nullptr); + EXPECT_EQ(secondary->resize_old_size(), 0U); + EXPECT_EQ(secondary->resize_new_size(), 0U); } TEST_F(FallbackAllocatorTest, ResizePrimaryFailure) { Layout old_layout = Layout::Of<uint32_t>(); void* ptr = allocator.Allocate(old_layout); ASSERT_NE(ptr, nullptr); - primary.Exhaust(); + primary->Exhaust(); size_t new_size = sizeof(uint32_t[3]); EXPECT_FALSE(allocator.Resize(ptr, old_layout, new_size)); - EXPECT_EQ(primary.resize_ptr(), ptr); - EXPECT_EQ(primary.resize_old_size(), old_layout.size()); - EXPECT_EQ(primary.resize_new_size(), new_size); + EXPECT_EQ(primary->resize_ptr(), ptr); + EXPECT_EQ(primary->resize_old_size(), old_layout.size()); + EXPECT_EQ(primary->resize_new_size(), new_size); // Secondary should not be touched. - EXPECT_EQ(secondary.resize_ptr(), nullptr); - EXPECT_EQ(secondary.resize_old_size(), 0U); - EXPECT_EQ(secondary.resize_new_size(), 0U); + EXPECT_EQ(secondary->resize_ptr(), nullptr); + EXPECT_EQ(secondary->resize_old_size(), 0U); + EXPECT_EQ(secondary->resize_new_size(), 0U); } TEST_F(FallbackAllocatorTest, ResizeSecondary) { - primary.Exhaust(); + primary->Exhaust(); Layout old_layout = Layout::Of<uint32_t>(); void* ptr = allocator.Allocate(old_layout); ASSERT_NE(ptr, nullptr); size_t new_size = sizeof(uint32_t[3]); EXPECT_TRUE(allocator.Resize(ptr, old_layout, new_size)); - EXPECT_EQ(secondary.resize_ptr(), ptr); - EXPECT_EQ(secondary.resize_old_size(), old_layout.size()); - EXPECT_EQ(secondary.resize_new_size(), new_size); + EXPECT_EQ(secondary->resize_ptr(), ptr); + EXPECT_EQ(secondary->resize_old_size(), old_layout.size()); + EXPECT_EQ(secondary->resize_new_size(), new_size); // Primary should not be touched. - EXPECT_EQ(primary.resize_ptr(), nullptr); - EXPECT_EQ(primary.resize_old_size(), 0U); - EXPECT_EQ(primary.resize_new_size(), 0U); + EXPECT_EQ(primary->resize_ptr(), nullptr); + EXPECT_EQ(primary->resize_old_size(), 0U); + EXPECT_EQ(primary->resize_new_size(), 0U); } TEST_F(FallbackAllocatorTest, ResizeSecondaryFailure) { - primary.Exhaust(); + primary->Exhaust(); Layout old_layout = Layout::Of<uint32_t>(); void* ptr = allocator.Allocate(old_layout); ASSERT_NE(ptr, nullptr); - secondary.Exhaust(); + secondary->Exhaust(); size_t new_size = sizeof(uint32_t[3]); EXPECT_FALSE(allocator.Resize(ptr, old_layout, new_size)); - EXPECT_EQ(secondary.resize_ptr(), ptr); - EXPECT_EQ(secondary.resize_old_size(), old_layout.size()); - EXPECT_EQ(secondary.resize_new_size(), new_size); + EXPECT_EQ(secondary->resize_ptr(), ptr); + EXPECT_EQ(secondary->resize_old_size(), old_layout.size()); + EXPECT_EQ(secondary->resize_new_size(), new_size); // Primary should not be touched. - EXPECT_EQ(primary.resize_ptr(), nullptr); - EXPECT_EQ(primary.resize_old_size(), 0U); - EXPECT_EQ(primary.resize_new_size(), 0U); + EXPECT_EQ(primary->resize_ptr(), nullptr); + EXPECT_EQ(primary->resize_old_size(), 0U); + EXPECT_EQ(primary->resize_new_size(), 0U); } TEST_F(FallbackAllocatorTest, ReallocateSameAllocator) { @@ -201,22 +199,22 @@ TEST_F(FallbackAllocatorTest, ReallocateSameAllocator) { size_t new_size = sizeof(uint32_t[3]); void* new_ptr = allocator.Reallocate(ptr1, old_layout, new_size); EXPECT_NE(new_ptr, nullptr); - EXPECT_EQ(primary.deallocate_ptr(), ptr1); - EXPECT_EQ(primary.deallocate_size(), old_layout.size()); - EXPECT_EQ(primary.allocate_size(), new_size); + EXPECT_EQ(primary->deallocate_ptr(), ptr1); + EXPECT_EQ(primary->deallocate_size(), old_layout.size()); + EXPECT_EQ(primary->allocate_size(), new_size); } TEST_F(FallbackAllocatorTest, ReallocateDifferentAllocator) { Layout old_layout = Layout::Of<uint32_t>(); void* ptr = allocator.Allocate(old_layout); - primary.Exhaust(); + primary->Exhaust(); size_t new_size = sizeof(uint32_t[3]); void* new_ptr = allocator.Reallocate(ptr, old_layout, new_size); EXPECT_NE(new_ptr, nullptr); - EXPECT_EQ(primary.deallocate_ptr(), ptr); - EXPECT_EQ(primary.deallocate_size(), old_layout.size()); - EXPECT_EQ(secondary.allocate_size(), new_size); + EXPECT_EQ(primary->deallocate_ptr(), ptr); + EXPECT_EQ(primary->deallocate_size(), old_layout.size()); + EXPECT_EQ(secondary->allocate_size(), new_size); } } // namespace diff --git a/pw_allocator/libc_allocator_test.cc b/pw_allocator/libc_allocator_test.cc index d69dcb072..967a1069d 100644 --- a/pw_allocator/libc_allocator_test.cc +++ b/pw_allocator/libc_allocator_test.cc @@ -22,7 +22,8 @@ namespace pw::allocator { // Test fixtures. -struct LibCAllocatorTest : ::testing::Test { +class LibCAllocatorTest : public ::testing::Test { + protected: LibCAllocator allocator; }; diff --git a/pw_allocator/public/pw_allocator/allocator_testing.h b/pw_allocator/public/pw_allocator/allocator_testing.h new file mode 100644 index 000000000..41077522d --- /dev/null +++ b/pw_allocator/public/pw_allocator/allocator_testing.h @@ -0,0 +1,134 @@ +// Copyright 2023 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. +#pragma once + +#include <array> +#include <cstddef> + +#include "gtest/gtest.h" +#include "pw_allocator/allocator.h" +#include "pw_allocator/block.h" +#include "pw_allocator/simple_allocator.h" +#include "pw_bytes/span.h" + +namespace pw::allocator::test { + +/// Simple memory allocator for testing. +/// +/// This allocator records the most recent parameters passed to the `Allocator` +/// interface methods, and returns them via accessors. +class AllocatorForTest : public Allocator { + public: + constexpr AllocatorForTest() = default; + ~AllocatorForTest() override; + + size_t allocate_size() const { return allocate_size_; } + void* deallocate_ptr() const { return deallocate_ptr_; } + size_t deallocate_size() const { return deallocate_size_; } + void* resize_ptr() const { return resize_ptr_; } + size_t resize_old_size() const { return resize_old_size_; } + size_t resize_new_size() const { return resize_new_size_; } + + /// Provides memory for the allocator to allocate from. + Status Init(ByteSpan bytes); + + /// Allocates all the memory from this object. + void Exhaust(); + + /// Resets the recorded parameters to an initial state. + void ResetParameters(); + + /// This frees all memory associated with this allocator. + void DeallocateAll(); + + private: + using BlockType = Block<>; + + /// @copydoc Allocator::Query + Status DoQuery(const void* ptr, size_t size, size_t alignment) const override; + + /// @copydoc Allocator::Allocate + void* DoAllocate(size_t size, size_t alignment) override; + + /// @copydoc Allocator::Deallocate + void DoDeallocate(void* ptr, size_t size, size_t alignment) override; + + /// @copydoc Allocator::Resize + bool DoResize(void* ptr, + size_t old_size, + size_t old_alignment, + size_t new_size) override; + + SimpleAllocator allocator_; + size_t allocate_size_ = 0; + void* deallocate_ptr_ = nullptr; + size_t deallocate_size_ = 0; + void* resize_ptr_ = nullptr; + size_t resize_old_size_ = 0; + size_t resize_new_size_ = 0; +}; + +/// Wraps a default-constructed type a buffer holding a region of memory. +/// +/// Although the type is arbitrary, the intended purpose of of this class is to +/// provide allocators with memory to use when testing. +/// +/// This class uses composition instead of inheritance in order to allow the +/// wrapped type's destructor to reference the memory without risk of a +/// use-after-free. As a result, the specific methods of the wrapped type +/// are not directly accesible. Instead, they can be accessed using the `*` and +/// `->` operators, e.g. +/// +/// @code{.cpp} +/// WithBuffer<MyAllocator, 256> allocator; +/// allocator->MethodSpecificToMyAllocator(); +/// @endcode +/// +/// Note that this class does NOT initialize the allocator, since initialization +/// is not specified as part of the `Allocator` interface and may vary from +/// allocator to allocator. As a result, typical usgae includes deriving a class +/// that initializes the wrapped allocator with the buffer in a constructor. See +/// `AllocatorForTestWithBuffer` below for an example. +/// +/// @tparam T The wrapped object. +/// @tparam kBufferSize The size of the backing memory, in bytes. +/// @tparam AlignType Buffer memory will be aligned to this type's +/// alignment boundary. +template <typename T, size_t kBufferSize, typename AlignType = uint8_t> +class WithBuffer { + public: + static constexpr size_t kCapacity = kBufferSize; + + std::byte* data() { return buffer_.data(); } + size_t size() const { return buffer_.size(); } + + T& operator*() { return obj_; } + T* operator->() { return &obj_; } + + private: + alignas(AlignType) std::array<std::byte, kBufferSize> buffer_; + T obj_; +}; + +/// An `AllocatorForTest` that is automatically initialized on construction. +template <size_t kBufferSize> +class AllocatorForTestWithBuffer + : public WithBuffer<AllocatorForTest, kBufferSize> { + public: + AllocatorForTestWithBuffer() { + EXPECT_EQ((*this)->Init(ByteSpan(this->data(), this->size())), OkStatus()); + } +}; + +} // namespace pw::allocator::test diff --git a/pw_allocator/public/pw_allocator/block.h b/pw_allocator/public/pw_allocator/block.h index d8d505fa5..4b5e7e594 100644 --- a/pw_allocator/public/pw_allocator/block.h +++ b/pw_allocator/public/pw_allocator/block.h @@ -151,53 +151,8 @@ class BaseBlock { /// contiguous region of memory returned from a call to `Init`. This block can /// be split into smaller blocks that refer to their neighbors. Neighboring /// blocks can be merged. These behaviors allows ``Allocator``s to track -/// allocated memory with a small amount of overhead. -/// -/// For example, the following is a simple but functional ``Allocator`` using -/// ``Block``: -/// -/// @code{.cpp} -/// // TODO(b/306686936): Consider moving this to a standalone example. -/// class SimpleAllocator { -/// public: -/// Status Init(ByteSpan region) { -/// auto result = Block<>::Init(region); -/// if (!result.ok()) { return result.status(); } -/// begin_ = *result; -/// end_ = begin_->Next(); -/// return OkStatus(); -/// } -/// -/// private: -/// void* DoAllocate(Layout layout) override { -/// for (auto* block = begin_; block != end_; block = block->Next()) { -/// if (block->InnerSize() >= layout.size()) { -/// if (auto result=Block<>::Split(block, layout.size()); result.ok()) { -/// // Try to merge the leftovers with the next block. -/// Block<>::MergeNext(*result).IgnoreError(); -/// } -/// block->MarkUsed(); -/// return block->UsableSpace(); -/// } -/// } -/// return nullptr; -/// } -/// -/// void DoDeallocate(void* ptr, Layout) override { -/// Block<>* block = Block<>::FromUsableSpace(ptr); -/// block->MarkFree(); -/// // Try to merge the released block with its neighbors. -/// Block<>::MergeNext(block).IgnoreError(); -/// block = block->Prev(); -/// Block<>::MergeNext(block).IgnoreError(); -/// } -/// -/// bool DoResize(void*, Layout, size_t) { -/// // Always reallocate. -/// return false; -/// } -/// }; -/// @endcode +/// allocated memory with a small amount of overhead. See +/// pw_allocator_private/simple_allocator.h for an example. /// /// Blocks will always be aligned to a `kAlignment boundary. Block sizes will /// always be rounded up to a multiple of `kAlignment`. diff --git a/pw_allocator/public/pw_allocator/simple_allocator.h b/pw_allocator/public/pw_allocator/simple_allocator.h new file mode 100644 index 000000000..98513426e --- /dev/null +++ b/pw_allocator/public/pw_allocator/simple_allocator.h @@ -0,0 +1,87 @@ +// Copyright 2023 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. +#pragma once + +#include "pw_allocator/allocator.h" +#include "pw_allocator/block.h" + +namespace pw::allocator { + +// DOCSTAG: [pw_allocator_examples_simple_allocator] +/// Simple allocator that uses a list of `Block`s. +class SimpleAllocator : public Allocator { + public: + using Block = pw::allocator::Block<>; + using Range = typename Block::Range; + + constexpr SimpleAllocator() = default; + + /// Initialize this allocator to allocate memory from `region`. + Status Init(ByteSpan region) { + auto result = Block::Init(region); + if (result.ok()) { + blocks_ = *result; + } + return result.status(); + } + + /// Return the range of blocks for this allocator. + Range blocks() { return Range(blocks_); } + + private: + /// @copydoc Allocator::Query + Status DoQuery(const void* ptr, size_t, size_t) const override { + for (auto* block : Range(blocks_)) { + if (block->UsableSpace() == ptr) { + return OkStatus(); + } + } + return Status::OutOfRange(); + } + + /// @copydoc Allocator::Allocate + void* DoAllocate(size_t size, size_t alignment) override { + for (auto* block : Range(blocks_)) { + if (Block::AllocFirst(block, size, alignment).ok()) { + return block->UsableSpace(); + } + } + return nullptr; + } + + /// @copydoc Allocator::Deallocate + void DoDeallocate(void* ptr, size_t, size_t) override { + if (ptr == nullptr) { + return; + } + auto* bytes = static_cast<std::byte*>(ptr); + Block* block = Block::FromUsableSpace(bytes); + Block::Free(block); + } + + /// @copydoc Allocator::Resize + bool DoResize(void* ptr, size_t, size_t, size_t new_size) override { + if (ptr == nullptr) { + return false; + } + auto* bytes = static_cast<std::byte*>(ptr); + Block* block = Block::FromUsableSpace(bytes); + return Block::Resize(block, new_size).ok(); + } + + Block* blocks_ = nullptr; +}; +// DOCSTAG: [pw_allocator_examples_simple_allocator] + +} // namespace pw::allocator diff --git a/pw_allocator/pw_allocator_private/allocator_testing.h b/pw_allocator/pw_allocator_private/allocator_testing.h deleted file mode 100644 index 030492836..000000000 --- a/pw_allocator/pw_allocator_private/allocator_testing.h +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2023 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. -#pragma once - -#include <array> -#include <cstddef> - -#include "pw_allocator/allocator.h" -#include "pw_allocator/block.h" -#include "pw_bytes/span.h" - -namespace pw::allocator::test { - -/// Fake memory allocator for testing. -/// -/// This allocator can return a fixed number of allocations made using an -/// internal buffer. It records the most recent parameters passed to the -/// `Allocator` interface methods, and returns them via accessors. -class FakeAllocator : public Allocator { - public: - constexpr FakeAllocator() = default; - - size_t allocate_size() const { return allocate_size_; } - void* deallocate_ptr() const { return deallocate_ptr_; } - size_t deallocate_size() const { return deallocate_size_; } - void* resize_ptr() const { return resize_ptr_; } - size_t resize_old_size() const { return resize_old_size_; } - size_t resize_new_size() const { return resize_new_size_; } - - /// Provides memory for the allocator to allocate from. - Status Initialize(ByteSpan buffer); - - /// Allocates all the memory from this object. - void Exhaust(); - - /// Resets the recorded parameters to an initial state. - void ResetParameters(); - - private: - using BlockType = Block<>; - - /// @copydoc Allocator::Query - Status DoQuery(const void* ptr, size_t size, size_t alignment) const override; - - /// @copydoc Allocator::Allocate - void* DoAllocate(size_t size, size_t alignment) override; - - /// @copydoc Allocator::Deallocate - void DoDeallocate(void* ptr, size_t size, size_t alignment) override; - - /// @copydoc Allocator::Resize - bool DoResize(void* ptr, - size_t old_size, - size_t old_alignment, - size_t new_size) override; - - BlockType* begin_ = nullptr; - BlockType* end_ = nullptr; - size_t allocate_size_ = 0; - void* deallocate_ptr_ = nullptr; - size_t deallocate_size_ = 0; - void* resize_ptr_ = nullptr; - size_t resize_old_size_ = 0; - size_t resize_new_size_ = 0; -}; - -} // namespace pw::allocator::test diff --git a/pw_allocator/simple_allocator_test.cc b/pw_allocator/simple_allocator_test.cc new file mode 100644 index 000000000..4904bfe3b --- /dev/null +++ b/pw_allocator/simple_allocator_test.cc @@ -0,0 +1,66 @@ +// Copyright 2023 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/simple_allocator.h" + +#include "gtest/gtest.h" +#include "pw_allocator/allocator_testing.h" + +namespace pw::allocator { + +// Size of the memory region to use in the tests below. +constexpr size_t kCapacity = 256; + +// An `SimpleAllocator` that is automatically initialized on construction. +class SimpleAllocatorWithBuffer + : public test::WithBuffer<SimpleAllocator, kCapacity> { + public: + SimpleAllocatorWithBuffer() { + EXPECT_EQ((*this)->Init(ByteSpan(this->data(), this->size())), OkStatus()); + } +}; + +// This is not meant to be a rigorous unit test of individual behaviors, as much +// as simply a way to demonstrate and exercise the simple allocator. +TEST(SimpleAllocatorTest, AllocateResizeDeallocate) { + SimpleAllocatorWithBuffer allocator; + + // Can allocate usable memory. + constexpr size_t kSize1 = kCapacity / 4; + constexpr Layout layout1 = Layout::Of<std::byte[kSize1]>(); + auto* ptr = static_cast<std::byte*>(allocator->Allocate(layout1)); + ASSERT_NE(ptr, nullptr); + memset(ptr, 0x5A, kSize1); + + // Can shrink memory. Contents are preserved. + constexpr size_t kSize2 = kCapacity / 8; + constexpr Layout layout2 = Layout::Of<std::byte[kSize2]>(); + EXPECT_TRUE(allocator->Resize(ptr, layout1, layout2.size())); + for (size_t i = 0; i < kSize2; ++i) { + EXPECT_EQ(size_t(ptr[i]), 0x5Au); + } + + // Can grow memory. Contents are preserved. + constexpr size_t kSize3 = kCapacity / 2; + constexpr Layout layout3 = Layout::Of<std::byte[kSize3]>(); + EXPECT_TRUE(allocator->Resize(ptr, layout2, layout3.size())); + for (size_t i = 0; i < kSize2; ++i) { + EXPECT_EQ(size_t(ptr[i]), 0x5Au); + } + + // Can free memory. + allocator->Deallocate(ptr, layout3); +} + +} // namespace pw::allocator diff --git a/pw_allocator/split_free_list_allocator_test.cc b/pw_allocator/split_free_list_allocator_test.cc index a3d7fab0c..ab4bab42b 100644 --- a/pw_allocator/split_free_list_allocator_test.cc +++ b/pw_allocator/split_free_list_allocator_test.cc @@ -15,6 +15,7 @@ #include "pw_allocator/split_free_list_allocator.h" #include "gtest/gtest.h" +#include "pw_allocator/allocator_testing.h" #include "pw_allocator/block.h" #include "pw_bytes/alignment.h" #include "pw_bytes/span.h" @@ -23,24 +24,39 @@ namespace pw::allocator { namespace { -// Test fixture. +// Test fixtures. + +// Size of the memory region to use in the tests below. +static constexpr size_t kCapacity = 256; + +// Minimum size of a "large" allocation; allocation less than this size are +// considered "small". +static constexpr size_t kThreshold = 64; + +// An `SplitFreeListAllocator` that is automatically initialized on +// construction. +using BlockType = Block<uint16_t, kCapacity>; +class SplitFreeListAllocatorWithBuffer + : public test:: + WithBuffer<SplitFreeListAllocator<BlockType>, kCapacity, BlockType> { + public: + SplitFreeListAllocatorWithBuffer() { + EXPECT_EQ((*this)->Init(ByteSpan(this->data(), this->size()), kThreshold), + OkStatus()); + } +}; +// Test case fixture that allows individual tests to cache allocations and +// release them automatically on tear-down. class SplitFreeListAllocatorTest : public ::testing::Test { protected: - static constexpr size_t kCapacity = 256; - - using BlockType = Block<uint16_t, kCapacity>; - static constexpr size_t kMaxSize = kCapacity - BlockType::kBlockOverhead; - static constexpr size_t kThreshold = 64; static constexpr size_t kNumPtrs = 16; void SetUp() override { for (size_t i = 0; i < kNumPtrs; ++i) { ptrs_[i] = nullptr; } - span_ = ByteSpan(buffer_.data(), buffer_.size()); - ASSERT_EQ(allocator_.Init(span_, kThreshold), OkStatus()); } // This method simply ensures the memory is usable by writing to it. @@ -51,14 +67,12 @@ class SplitFreeListAllocatorTest : public ::testing::Test { if (ptrs_[i] != nullptr) { // `SplitFreeListAllocator::Deallocate` doesn't actually use the layout, // as the information it needs is encoded in the blocks. - allocator_.Deallocate(ptrs_[i], Layout::Of<void*>()); + allocator_->Deallocate(ptrs_[i], Layout::Of<void*>()); } } } - alignas(BlockType) std::array<std::byte, kCapacity> buffer_; - ByteSpan span_; - SplitFreeListAllocator<BlockType> allocator_; + SplitFreeListAllocatorWithBuffer allocator_; // Tests can store allocations in this array to have them automatically // freed in `TearDown`, including on ASSERT failure. If pointers are manually @@ -72,15 +86,16 @@ TEST_F(SplitFreeListAllocatorTest, InitUnaligned) { // The test fixture uses aligned memory to make it easier to reason about // allocations, but that isn't strictly required. SplitFreeListAllocator<Block<>> unaligned; - EXPECT_EQ(unaligned.Init(span_.subspan(1), kThreshold), OkStatus()); + ByteSpan bytes(allocator_.data(), allocator_.size()); + EXPECT_EQ(unaligned.Init(bytes.subspan(1), kThreshold), OkStatus()); } TEST_F(SplitFreeListAllocatorTest, AllocateLarge) { constexpr Layout layout = Layout::Of<std::byte[kThreshold]>(); - ptrs_[0] = allocator_.Allocate(layout); + ptrs_[0] = allocator_->Allocate(layout); ASSERT_NE(ptrs_[0], nullptr); - EXPECT_GE(ptrs_[0], buffer_.data()); - EXPECT_LT(ptrs_[0], buffer_.data() + buffer_.size()); + EXPECT_GE(ptrs_[0], allocator_.data()); + EXPECT_LT(ptrs_[0], allocator_.data() + allocator_.size()); UseMemory(ptrs_[0], layout.size()); } @@ -88,27 +103,27 @@ TEST_F(SplitFreeListAllocatorTest, AllocateSmall) { // Returned pointer should not be from the beginning, but should still be in // range. Exact pointer depends on allocator's minimum allocation size. constexpr Layout layout = Layout::Of<uint8_t>(); - ptrs_[0] = allocator_.Allocate(layout); + ptrs_[0] = allocator_->Allocate(layout); ASSERT_NE(ptrs_[0], nullptr); - EXPECT_GT(ptrs_[0], buffer_.data()); - EXPECT_LT(ptrs_[0], buffer_.data() + buffer_.size()); + EXPECT_GT(ptrs_[0], allocator_.data()); + EXPECT_LT(ptrs_[0], allocator_.data() + allocator_.size()); UseMemory(ptrs_[0], layout.size()); } TEST_F(SplitFreeListAllocatorTest, AllocateTooLarge) { - ptrs_[0] = allocator_.Allocate(Layout::Of<std::byte[kCapacity * 2]>()); + ptrs_[0] = allocator_->Allocate(Layout::Of<std::byte[kCapacity * 2]>()); EXPECT_EQ(ptrs_[0], nullptr); } TEST_F(SplitFreeListAllocatorTest, AllocateLargeAlignment) { constexpr size_t kSize = sizeof(uint32_t); constexpr size_t kAlignment = 64; - ptrs_[0] = allocator_.AllocateUnchecked(kSize, kAlignment); + ptrs_[0] = allocator_->AllocateUnchecked(kSize, kAlignment); ASSERT_NE(ptrs_[0], nullptr); EXPECT_EQ(reinterpret_cast<uintptr_t>(ptrs_[0]) % kAlignment, 0U); UseMemory(ptrs_[0], kSize); - ptrs_[1] = allocator_.AllocateUnchecked(kSize, kAlignment); + ptrs_[1] = allocator_->AllocateUnchecked(kSize, kAlignment); ASSERT_NE(ptrs_[1], nullptr); EXPECT_EQ(reinterpret_cast<uintptr_t>(ptrs_[1]) % kAlignment, 0U); UseMemory(ptrs_[0], kSize); @@ -116,7 +131,8 @@ TEST_F(SplitFreeListAllocatorTest, AllocateLargeAlignment) { TEST_F(SplitFreeListAllocatorTest, AllocateFromUnaligned) { SplitFreeListAllocator<Block<>> unaligned; - ASSERT_EQ(unaligned.Init(span_.subspan(1), kThreshold), OkStatus()); + ByteSpan bytes(allocator_.data(), allocator_.size()); + ASSERT_EQ(unaligned.Init(bytes.subspan(1), kThreshold), OkStatus()); constexpr Layout layout = Layout::Of<std::byte[kThreshold + 8]>(); void* ptr = unaligned.Allocate(layout); @@ -127,9 +143,9 @@ TEST_F(SplitFreeListAllocatorTest, AllocateFromUnaligned) { TEST_F(SplitFreeListAllocatorTest, AllocateAlignmentFailure) { // Determine the total number of available bytes. - auto base = reinterpret_cast<uintptr_t>(buffer_.data()); + auto base = reinterpret_cast<uintptr_t>(allocator_.data()); uintptr_t addr = AlignUp(base, BlockType::kAlignment); - size_t outer_size = buffer_.size() - (addr - base); + size_t outer_size = allocator_.size() - (addr - base); // The first block is large.... addr += BlockType::kBlockOverhead + kThreshold; @@ -142,43 +158,43 @@ TEST_F(SplitFreeListAllocatorTest, AllocateAlignmentFailure) { } // And the last block consumes the remaining space. - // size_t outer_size = allocator_.begin()->OuterSize(); + // size_t outer_size = allocator_->begin()->OuterSize(); size_t inner_size1 = next - addr; size_t inner_size2 = kThreshold * 2; size_t inner_size3 = outer_size - (BlockType::kBlockOverhead * 3 + inner_size1 + inner_size2); // Allocate all the blocks. - ptrs_[0] = allocator_.AllocateUnchecked(inner_size1, 1); + ptrs_[0] = allocator_->AllocateUnchecked(inner_size1, 1); ASSERT_NE(ptrs_[0], nullptr); - ptrs_[1] = allocator_.AllocateUnchecked(inner_size2, 1); + ptrs_[1] = allocator_->AllocateUnchecked(inner_size2, 1); ASSERT_NE(ptrs_[1], nullptr); - ptrs_[2] = allocator_.AllocateUnchecked(inner_size3, 1); + ptrs_[2] = allocator_->AllocateUnchecked(inner_size3, 1); ASSERT_NE(ptrs_[2], nullptr); // If done correctly, the second block's usable space should be unaligned. EXPECT_NE(reinterpret_cast<uintptr_t>(ptrs_[1]) % kAlignment, 0U); // Free the second region. This leaves an unaligned region available. - allocator_.DeallocateUnchecked(ptrs_[1], inner_size2, 1); + allocator_->DeallocateUnchecked(ptrs_[1], inner_size2, 1); ptrs_[1] = nullptr; // The allocator should be unable to create an aligned region.. - ptrs_[3] = allocator_.AllocateUnchecked(inner_size2, kAlignment); + ptrs_[3] = allocator_->AllocateUnchecked(inner_size2, kAlignment); EXPECT_EQ(ptrs_[3], nullptr); } TEST_F(SplitFreeListAllocatorTest, DeallocateNull) { constexpr Layout layout = Layout::Of<uint8_t>(); - allocator_.Deallocate(nullptr, layout); + allocator_->Deallocate(nullptr, layout); } TEST_F(SplitFreeListAllocatorTest, DeallocateShuffled) { constexpr Layout layout = Layout::Of<std::byte[32]>(); // Allocate until the pool is exhausted. for (size_t i = 0; i < kNumPtrs; ++i) { - ptrs_[i] = allocator_.Allocate(layout); + ptrs_[i] = allocator_->Allocate(layout); if (ptrs_[i] == nullptr) { break; } @@ -194,7 +210,7 @@ TEST_F(SplitFreeListAllocatorTest, DeallocateShuffled) { } // Deallocate everything. for (size_t i = 0; i < kNumPtrs; ++i) { - allocator_.Deallocate(ptrs_[i], layout); + allocator_->Deallocate(ptrs_[i], layout); ptrs_[i] = nullptr; } } @@ -206,23 +222,23 @@ TEST_F(SplitFreeListAllocatorTest, IterateOverBlocks) { // Allocate eight blocks of alternating sizes. After this, the will also be a // ninth, unallocated block of the remaining memory. for (size_t i = 0; i < 4; ++i) { - ptrs_[i] = allocator_.Allocate(layout1); + ptrs_[i] = allocator_->Allocate(layout1); ASSERT_NE(ptrs_[i], nullptr); - ptrs_[i + 4] = allocator_.Allocate(layout2); + ptrs_[i + 4] = allocator_->Allocate(layout2); ASSERT_NE(ptrs_[i + 4], nullptr); } // Deallocate every other block. After this there will be four more // unallocated blocks, for a total of five. for (size_t i = 0; i < 4; ++i) { - allocator_.Deallocate(ptrs_[i], layout1); + allocator_->Deallocate(ptrs_[i], layout1); } // Count the blocks. The unallocated ones vary in size, but the allocated ones // should all be the same. size_t free_count = 0; size_t used_count = 0; - for (auto* block : allocator_.blocks()) { + for (auto* block : allocator_->blocks()) { if (block->Used()) { EXPECT_GE(block->InnerSize(), layout2.size()); ++used_count; @@ -236,116 +252,116 @@ TEST_F(SplitFreeListAllocatorTest, IterateOverBlocks) { TEST_F(SplitFreeListAllocatorTest, QueryLargeValid) { constexpr Layout layout = Layout::Of<std::byte[kThreshold * 2]>(); - ptrs_[0] = allocator_.Allocate(layout); - EXPECT_EQ(allocator_.Query(ptrs_[0], layout), OkStatus()); + ptrs_[0] = allocator_->Allocate(layout); + EXPECT_EQ(allocator_->Query(ptrs_[0], layout), OkStatus()); } TEST_F(SplitFreeListAllocatorTest, QuerySmallValid) { constexpr Layout layout = Layout::Of<uint8_t>(); - ptrs_[0] = allocator_.Allocate(layout); - EXPECT_EQ(allocator_.Query(ptrs_[0], layout), OkStatus()); + ptrs_[0] = allocator_->Allocate(layout); + EXPECT_EQ(allocator_->Query(ptrs_[0], layout), OkStatus()); } TEST_F(SplitFreeListAllocatorTest, QueryInvalidPtr) { constexpr Layout layout = Layout::Of<SplitFreeListAllocatorTest>(); - EXPECT_EQ(allocator_.Query(this, layout), Status::OutOfRange()); + EXPECT_EQ(allocator_->Query(this, layout), Status::OutOfRange()); } TEST_F(SplitFreeListAllocatorTest, ResizeNull) { constexpr Layout old_layout = Layout::Of<uint8_t>(); size_t new_size = 1; - EXPECT_FALSE(allocator_.Resize(nullptr, old_layout, new_size)); + EXPECT_FALSE(allocator_->Resize(nullptr, old_layout, new_size)); } TEST_F(SplitFreeListAllocatorTest, ResizeSame) { constexpr Layout old_layout = Layout::Of<uint32_t>(); - ptrs_[0] = allocator_.Allocate(old_layout); + ptrs_[0] = allocator_->Allocate(old_layout); ASSERT_NE(ptrs_[0], nullptr); constexpr Layout new_layout = Layout::Of<uint32_t>(); - EXPECT_TRUE(allocator_.Resize(ptrs_[0], old_layout, new_layout.size())); + EXPECT_TRUE(allocator_->Resize(ptrs_[0], old_layout, new_layout.size())); ASSERT_NE(ptrs_[0], nullptr); UseMemory(ptrs_[0], new_layout.size()); } TEST_F(SplitFreeListAllocatorTest, ResizeLargeSmaller) { constexpr Layout old_layout = Layout::Of<std::byte[kMaxSize]>(); - ptrs_[0] = allocator_.Allocate(old_layout); + ptrs_[0] = allocator_->Allocate(old_layout); ASSERT_NE(ptrs_[0], nullptr); // Shrinking always succeeds. constexpr Layout new_layout = Layout::Of<std::byte[kThreshold]>(); - EXPECT_TRUE(allocator_.Resize(ptrs_[0], old_layout, new_layout.size())); + EXPECT_TRUE(allocator_->Resize(ptrs_[0], old_layout, new_layout.size())); ASSERT_NE(ptrs_[0], nullptr); UseMemory(ptrs_[0], new_layout.size()); } TEST_F(SplitFreeListAllocatorTest, ResizeLargeLarger) { constexpr Layout old_layout = Layout::Of<std::byte[kThreshold]>(); - ptrs_[0] = allocator_.Allocate(old_layout); + ptrs_[0] = allocator_->Allocate(old_layout); ASSERT_NE(ptrs_[0], nullptr); // Nothing after ptr, so `Resize` should succeed. constexpr Layout new_layout = Layout::Of<std::byte[kMaxSize]>(); - EXPECT_TRUE(allocator_.Resize(ptrs_[0], old_layout, new_layout.size())); + EXPECT_TRUE(allocator_->Resize(ptrs_[0], old_layout, new_layout.size())); ASSERT_NE(ptrs_[0], nullptr); UseMemory(ptrs_[0], new_layout.size()); } TEST_F(SplitFreeListAllocatorTest, ResizeLargeLargerFailure) { constexpr Layout old_layout = Layout::Of<std::byte[kThreshold]>(); - ptrs_[0] = allocator_.Allocate(old_layout); + ptrs_[0] = allocator_->Allocate(old_layout); ASSERT_NE(ptrs_[0], nullptr); - ptrs_[1] = allocator_.Allocate(old_layout); + ptrs_[1] = allocator_->Allocate(old_layout); ASSERT_NE(ptrs_[1], nullptr); // Memory after ptr is already allocated, so `Resize` should fail. - EXPECT_FALSE(allocator_.Resize(ptrs_[0], old_layout, kMaxSize)); + EXPECT_FALSE(allocator_->Resize(ptrs_[0], old_layout, kMaxSize)); } TEST_F(SplitFreeListAllocatorTest, ResizeLargeSmallerAcrossThreshold) { constexpr Layout old_layout = Layout::Of<std::byte[kThreshold]>(); - ptrs_[0] = allocator_.Allocate(old_layout); + ptrs_[0] = allocator_->Allocate(old_layout); ASSERT_NE(ptrs_[0], nullptr); // Shrinking succeeds, and the pointer is unchanged even though it is now // below the threshold. constexpr Layout new_layout = Layout::Of<std::byte[kThreshold / 4]>(); - EXPECT_TRUE(allocator_.Resize(ptrs_[0], old_layout, new_layout.size())); + EXPECT_TRUE(allocator_->Resize(ptrs_[0], old_layout, new_layout.size())); ASSERT_NE(ptrs_[0], nullptr); UseMemory(ptrs_[0], new_layout.size()); } TEST_F(SplitFreeListAllocatorTest, ResizeSmallSmaller) { constexpr Layout old_layout = Layout::Of<uint32_t>(); - ptrs_[0] = allocator_.Allocate(old_layout); + ptrs_[0] = allocator_->Allocate(old_layout); ASSERT_NE(ptrs_[0], nullptr); // Shrinking always succeeds. constexpr Layout new_layout = Layout::Of<uint8_t>(); - EXPECT_TRUE(allocator_.Resize(ptrs_[0], old_layout, new_layout.size())); + EXPECT_TRUE(allocator_->Resize(ptrs_[0], old_layout, new_layout.size())); } TEST_F(SplitFreeListAllocatorTest, ResizeSmallLarger) { // First, allocate a trailing block. constexpr Layout layout1 = Layout::Of<std::byte[kThreshold / 4]>(); - ptrs_[0] = allocator_.Allocate(layout1); + ptrs_[0] = allocator_->Allocate(layout1); ASSERT_NE(ptrs_[0], nullptr); // Next allocate the memory to be resized. constexpr Layout old_layout = Layout::Of<std::byte[kThreshold / 4]>(); - ptrs_[1] = allocator_.Allocate(old_layout); + ptrs_[1] = allocator_->Allocate(old_layout); ASSERT_NE(ptrs_[1], nullptr); // Now free the trailing block. - allocator_.Deallocate(ptrs_[0], layout1); + allocator_->Deallocate(ptrs_[0], layout1); ptrs_[0] = nullptr; // And finally, resize. Since the memory after the block is available and big // enough, `Resize` should succeed. constexpr Layout new_layout = Layout::Of<std::byte[kThreshold / 2]>(); - EXPECT_TRUE(allocator_.Resize(ptrs_[1], old_layout, new_layout.size())); + EXPECT_TRUE(allocator_->Resize(ptrs_[1], old_layout, new_layout.size())); ASSERT_NE(ptrs_[1], nullptr); UseMemory(ptrs_[1], new_layout.size()); } @@ -353,48 +369,48 @@ TEST_F(SplitFreeListAllocatorTest, ResizeSmallLarger) { TEST_F(SplitFreeListAllocatorTest, ResizeSmallLargerFailure) { // First, allocate a trailing block. constexpr Layout layout1 = Layout::Of<std::byte[kThreshold / 4]>(); - ptrs_[0] = allocator_.Allocate(layout1); + ptrs_[0] = allocator_->Allocate(layout1); ASSERT_NE(ptrs_[0], nullptr); // Next allocate the memory to be resized. constexpr Layout old_layout = Layout::Of<std::byte[kThreshold / 4]>(); - ptrs_[1] = allocator_.Allocate(old_layout); + ptrs_[1] = allocator_->Allocate(old_layout); ASSERT_NE(ptrs_[1], nullptr); // Now free the trailing block. - allocator_.Deallocate(ptrs_[0], layout1); + allocator_->Deallocate(ptrs_[0], layout1); ptrs_[0] = nullptr; // And finally, resize. Since the memory after the block is available but not // big enough, `Resize` should fail. size_t new_size = 48; - EXPECT_FALSE(allocator_.Resize(ptrs_[1], old_layout, new_size)); + EXPECT_FALSE(allocator_->Resize(ptrs_[1], old_layout, new_size)); } TEST_F(SplitFreeListAllocatorTest, ResizeSmallLargerAcrossThreshold) { // First, allocate several trailing block. constexpr Layout layout1 = Layout::Of<std::byte[kThreshold / 2]>(); - ptrs_[0] = allocator_.Allocate(layout1); + ptrs_[0] = allocator_->Allocate(layout1); ASSERT_NE(ptrs_[0], nullptr); - ptrs_[1] = allocator_.Allocate(layout1); + ptrs_[1] = allocator_->Allocate(layout1); ASSERT_NE(ptrs_[1], nullptr); // Next allocate the memory to be resized. constexpr Layout old_layout = Layout::Of<std::byte[kThreshold / 4]>(); - ptrs_[2] = allocator_.Allocate(old_layout); + ptrs_[2] = allocator_->Allocate(old_layout); EXPECT_NE(ptrs_[2], nullptr); // Now free the trailing blocks. - allocator_.Deallocate(ptrs_[0], layout1); + allocator_->Deallocate(ptrs_[0], layout1); ptrs_[0] = nullptr; - allocator_.Deallocate(ptrs_[1], layout1); + allocator_->Deallocate(ptrs_[1], layout1); ptrs_[1] = nullptr; // Growing succeeds, and the pointer is unchanged even though it is now // above the threshold. constexpr Layout new_layout = Layout::Of<std::byte[kThreshold]>(); - EXPECT_TRUE(allocator_.Resize(ptrs_[2], old_layout, new_layout.size())); + EXPECT_TRUE(allocator_->Resize(ptrs_[2], old_layout, new_layout.size())); ASSERT_NE(ptrs_[2], nullptr); UseMemory(ptrs_[2], new_layout.size()); } diff --git a/pw_allocator/unique_ptr_test.cc b/pw_allocator/unique_ptr_test.cc index 9382a2bf5..c0aa57cce 100644 --- a/pw_allocator/unique_ptr_test.cc +++ b/pw_allocator/unique_ptr_test.cc @@ -16,18 +16,12 @@ #include "gtest/gtest.h" #include "pw_allocator/allocator.h" -#include "pw_allocator_private/allocator_testing.h" +#include "pw_allocator/allocator_testing.h" namespace pw::allocator { namespace { -class FakeAllocWithBuffer : public test::FakeAllocator { - public: - FakeAllocWithBuffer() { EXPECT_EQ(Initialize(buffer_), OkStatus()); } - - private: - std::array<std::byte, 256> buffer_ = {}; -}; +using FakeAllocWithBuffer = test::AllocatorForTestWithBuffer<256>; TEST(UniquePtr, DefaultInitializationIsNullptr) { UniquePtr<int> empty; @@ -42,7 +36,7 @@ TEST(UniquePtr, OperatorEqNullptrOnEmptyUniquePtrSucceeds) { TEST(UniquePtr, OperatorEqNullptrAfterMakeUniqueFails) { FakeAllocWithBuffer alloc; - std::optional<UniquePtr<int>> ptr_opt = alloc.MakeUnique<int>(5); + std::optional<UniquePtr<int>> ptr_opt = alloc->MakeUnique<int>(5); ASSERT_TRUE(ptr_opt.has_value()); UniquePtr<int> ptr = std::move(*ptr_opt); EXPECT_TRUE(ptr != nullptr); @@ -52,7 +46,7 @@ TEST(UniquePtr, OperatorEqNullptrAfterMakeUniqueFails) { TEST(UniquePtr, OperatorEqNullptrAfterMakeUniqueNullptrTypeFails) { FakeAllocWithBuffer alloc; std::optional<UniquePtr<std::nullptr_t>> ptr_opt = - alloc.MakeUnique<std::nullptr_t>(nullptr); + alloc->MakeUnique<std::nullptr_t>(nullptr); ASSERT_TRUE(ptr_opt.has_value()); UniquePtr<std::nullptr_t> ptr = std::move(*ptr_opt); EXPECT_TRUE(ptr != nullptr); @@ -86,7 +80,7 @@ TEST(UniquePtr, MakeUniqueForwardsConstructorArguments) { FakeAllocWithBuffer alloc; MoveOnly mo(6); std::optional<UniquePtr<BuiltWithMoveOnly>> ptr = - alloc.MakeUnique<BuiltWithMoveOnly>(std::move(mo)); + alloc->MakeUnique<BuiltWithMoveOnly>(std::move(mo)); ASSERT_TRUE(ptr.has_value()); EXPECT_EQ((*ptr)->Value(), 6); } @@ -97,17 +91,17 @@ TEST(UniquePtr, MoveConstructsFromSubClassAndFreesTotalSize) { std::array<std::byte, 128> mem; }; FakeAllocWithBuffer alloc; - std::optional<UniquePtr<LargerSub>> ptr_opt = alloc.MakeUnique<LargerSub>(); + std::optional<UniquePtr<LargerSub>> ptr_opt = alloc->MakeUnique<LargerSub>(); ASSERT_TRUE(ptr_opt.has_value()); - EXPECT_EQ(alloc.allocate_size(), 128ul); + EXPECT_EQ(alloc->allocate_size(), 128ul); UniquePtr<LargerSub> ptr = std::move(*ptr_opt); UniquePtr<Base> base_ptr(std::move(ptr)); - EXPECT_EQ(alloc.deallocate_size(), 0ul); + EXPECT_EQ(alloc->deallocate_size(), 0ul); // The size that is deallocated here should be the size of the larger // subclass, not the size of the smaller base class. base_ptr.Reset(); - EXPECT_EQ(alloc.deallocate_size(), 128ul); + EXPECT_EQ(alloc->deallocate_size(), 128ul); } TEST(UniquePtr, MoveAssignsFromSubClassAndFreesTotalSize) { @@ -116,17 +110,17 @@ TEST(UniquePtr, MoveAssignsFromSubClassAndFreesTotalSize) { std::array<std::byte, 128> mem; }; FakeAllocWithBuffer alloc; - std::optional<UniquePtr<LargerSub>> ptr_opt = alloc.MakeUnique<LargerSub>(); + std::optional<UniquePtr<LargerSub>> ptr_opt = alloc->MakeUnique<LargerSub>(); ASSERT_TRUE(ptr_opt.has_value()); - EXPECT_EQ(alloc.allocate_size(), 128ul); + EXPECT_EQ(alloc->allocate_size(), 128ul); UniquePtr<LargerSub> ptr = std::move(*ptr_opt); UniquePtr<Base> base_ptr = std::move(ptr); - EXPECT_EQ(alloc.deallocate_size(), 0ul); + EXPECT_EQ(alloc->deallocate_size(), 0ul); // The size that is deallocated here should be the size of the larger // subclass, not the size of the smaller base class. base_ptr.Reset(); - EXPECT_EQ(alloc.deallocate_size(), 128ul); + EXPECT_EQ(alloc->deallocate_size(), 128ul); } TEST(UniquePtr, DestructorDestroysAndFrees) { @@ -141,15 +135,15 @@ TEST(UniquePtr, DestructorDestroysAndFrees) { }; FakeAllocWithBuffer alloc; std::optional<UniquePtr<DestructorCounter>> ptr_opt = - alloc.MakeUnique<DestructorCounter>(count); + alloc->MakeUnique<DestructorCounter>(count); ASSERT_TRUE(ptr_opt.has_value()); EXPECT_EQ(count, 0); - EXPECT_EQ(alloc.deallocate_size(), 0ul); + EXPECT_EQ(alloc->deallocate_size(), 0ul); ptr_opt.reset(); // clear the optional, destroying the UniquePtr EXPECT_EQ(count, 1); - EXPECT_EQ(alloc.deallocate_size(), sizeof(DestructorCounter)); + EXPECT_EQ(alloc->deallocate_size(), sizeof(DestructorCounter)); } } // namespace -} // namespace pw::allocator
\ No newline at end of file +} // namespace pw::allocator |