xref: /aosp_15_r20/external/pigweed/pw_allocator/public/pw_allocator/block_allocator_testing.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include <cstddef>
17 #include <cstdint>
18 
19 #include "pw_allocator/block/detailed_block.h"
20 #include "pw_allocator/block/testing.h"
21 #include "pw_allocator/block_allocator.h"
22 #include "pw_status/status.h"
23 #include "pw_unit_test/framework.h"
24 
25 namespace pw::allocator::test {
26 
27 /// Test fixture responsible for managing a memory region and an allocator that
28 /// allocates block of memory from it.
29 ///
30 /// This base class contains all the code that does not depend specific
31 /// `Block` or `BlockAllocator` types.
32 class BlockAllocatorTestBase : public ::testing::Test {
33  public:
34   static constexpr size_t kDefaultBlockOverhead =
35       DetailedBlock<>::kBlockOverhead;
36 
37   // Size of the memory region to use in the tests below.
38   // This must be large enough so that BlockType::Init does not fail.
39   static constexpr size_t kCapacity = 1024;
40 
41   // The number of allocated pointers cached by the test fixture.
42   static constexpr size_t kNumPtrs = 16;
43 
44   // Represents the sizes of various allocations.
45   static constexpr size_t kLargeInnerSize = kCapacity / 8;
46   static constexpr size_t kLargeOuterSize =
47       kDefaultBlockOverhead + kLargeInnerSize;
48 
49   static constexpr size_t kSmallInnerSize = kDefaultBlockOverhead * 2;
50   static constexpr size_t kSmallOuterSize =
51       kDefaultBlockOverhead + kSmallInnerSize;
52 
53   static constexpr size_t kSmallerOuterSize = kSmallInnerSize;
54   static constexpr size_t kLargerOuterSize =
55       kLargeOuterSize + kSmallerOuterSize;
56 
57  protected:
58   // Test fixtures.
59   void SetUp() override;
60 
61   /// Returns the underlying memory region.
62   virtual ByteSpan GetBytes() = 0;
63 
64   /// Initialize the allocator with a region of memory and return it.
65   virtual Allocator& GetGenericAllocator() = 0;
66 
67   /// Initialize the allocator with a sequence of preallocated blocks and return
68   /// it.
69   ///
70   /// See also ``Preallocation``.
71   virtual Allocator& GetGenericAllocator(
72       std::initializer_list<Preallocation> preallocations) = 0;
73 
74   /// Gets the next allocation from an allocated pointer.
75   virtual void* NextAfter(size_t index) = 0;
76 
77   /// Store an allocated pointer in the test's cache of pointers.
78   void Store(size_t index, void* ptr);
79 
80   /// Retrieve an allocated pointer from the test's cache of pointers.
81   void* Fetch(size_t index);
82 
83   /// Swaps the pointer at indices `i` and `j`.
84   void Swap(size_t i, size_t j);
85 
86   /// Ensures the memory is usable by writing to it.
87   void UseMemory(void* ptr, size_t size);
88 
89   // Unit tests.
90   void GetCapacity();
91   void AllocateLarge();
92   void AllocateSmall();
93   void AllocateTooLarge();
94   void AllocateLargeAlignment();
95   void AllocateAlignmentFailure();
96   void DeallocateNull();
97   void DeallocateShuffled();
98   void ResizeNull();
99   void ResizeLargeSame();
100   void ResizeLargeSmaller();
101   void ResizeLargeLarger();
102   void ResizeLargeLargerFailure();
103   void ResizeSmallSame();
104   void ResizeSmallSmaller();
105   void ResizeSmallLarger();
106   void ResizeSmallLargerFailure();
107 
108  private:
109   std::array<void*, kNumPtrs> ptrs_;
110 };
111 
112 /// Test fixture responsible for managing a memory region and an allocator that
113 /// allocates block of memory from it.
114 ///
115 /// This derived class contains all the code that depends specific `Block` or
116 /// `BlockAllocator` types.
117 ///
118 /// @tparam BlockAllocatorType  The type of the `BlockAllocator` being tested.
119 template <typename BlockAllocatorType>
120 class BlockAllocatorTest : public BlockAllocatorTestBase {
121  public:
122   using BlockType = typename BlockAllocatorType::BlockType;
123 
124  protected:
125   // Test fixtures.
BlockAllocatorTest(BlockAllocatorType & allocator)126   BlockAllocatorTest(BlockAllocatorType& allocator) : allocator_(allocator) {
127     bytes_ = ByteSpan(buffer_);
128   }
129 
GetBytes()130   ByteSpan GetBytes() override { return bytes_; }
131 
GetGenericAllocator()132   Allocator& GetGenericAllocator() override { return GetAllocator(); }
133 
134   BlockAllocatorType& GetAllocator();
135 
GetGenericAllocator(std::initializer_list<Preallocation> preallocations)136   Allocator& GetGenericAllocator(
137       std::initializer_list<Preallocation> preallocations) override {
138     return GetAllocator(preallocations);
139   }
140 
141   BlockAllocatorType& GetAllocator(
142       std::initializer_list<Preallocation> preallocations);
143 
144   void* NextAfter(size_t index) override;
145 
146   void TearDown() override;
147 
148   // Unit tests.
149   static void AutomaticallyInit(BlockAllocatorType& allocator);
150   void ExplicitlyInit(BlockAllocatorType& allocator);
151   void IterateOverBlocks();
152   void MeasureFragmentation();
153   void PoisonPeriodically();
154 
155  private:
156   BlockAllocatorType& allocator_;
157   alignas(BlockType::kAlignment) std::array<std::byte, kCapacity> buffer_;
158   ByteSpan bytes_;
159 };
160 
161 // Test fixture template method implementations.
162 
163 template <typename BlockAllocatorType>
GetAllocator()164 BlockAllocatorType& BlockAllocatorTest<BlockAllocatorType>::GetAllocator() {
165   allocator_.Init(GetBytes());
166   return allocator_;
167 }
168 
169 template <typename BlockAllocatorType>
GetAllocator(std::initializer_list<Preallocation> preallocations)170 BlockAllocatorType& BlockAllocatorTest<BlockAllocatorType>::GetAllocator(
171     std::initializer_list<Preallocation> preallocations) {
172   auto* first = Preallocate<BlockType>(GetBytes(), preallocations);
173   size_t index = 0;
174   for (BlockType* block = first; block != nullptr; block = block->Next()) {
175     Store(index, block->IsFree() ? nullptr : block->UsableSpace());
176     ++index;
177   }
178   allocator_.Init(first);
179   return allocator_;
180 }
181 
182 template <typename BlockAllocatorType>
NextAfter(size_t index)183 void* BlockAllocatorTest<BlockAllocatorType>::NextAfter(size_t index) {
184   void* ptr = Fetch(index);
185   if (ptr == nullptr) {
186     return nullptr;
187   }
188 
189   auto* block = BlockType::FromUsableSpace(ptr);
190   block = block->Next();
191   while (block != nullptr && block->IsFree()) {
192     block = block->Next();
193   }
194   return block == nullptr ? nullptr : block->UsableSpace();
195 }
196 
197 template <typename BlockAllocatorType>
TearDown()198 void BlockAllocatorTest<BlockAllocatorType>::TearDown() {
199   for (size_t i = 0; i < kNumPtrs; ++i) {
200     void* ptr = Fetch(i);
201     if (ptr != nullptr) {
202       allocator_.Deallocate(ptr);
203     }
204   }
205   allocator_.Reset();
206 }
207 
208 // Unit tests template method implementations.
209 
210 template <typename BlockAllocatorType>
AutomaticallyInit(BlockAllocatorType & allocator)211 void BlockAllocatorTest<BlockAllocatorType>::AutomaticallyInit(
212     BlockAllocatorType& allocator) {
213   EXPECT_NE(*(allocator.blocks().begin()), nullptr);
214 }
215 
216 template <typename BlockAllocatorType>
ExplicitlyInit(BlockAllocatorType & allocator)217 void BlockAllocatorTest<BlockAllocatorType>::ExplicitlyInit(
218     BlockAllocatorType& allocator) {
219   EXPECT_EQ(*(allocator.blocks().begin()), nullptr);
220   allocator.Init(GetBytes());
221   EXPECT_NE(*(allocator.blocks().begin()), nullptr);
222 }
223 
224 template <typename BlockAllocatorType>
IterateOverBlocks()225 void BlockAllocatorTest<BlockAllocatorType>::IterateOverBlocks() {
226   Allocator& allocator = GetGenericAllocator({
227       {kSmallOuterSize, Preallocation::kFree},
228       {kLargeOuterSize, Preallocation::kUsed},
229       {kSmallOuterSize, Preallocation::kFree},
230       {kLargeOuterSize, Preallocation::kUsed},
231       {kSmallOuterSize, Preallocation::kFree},
232       {kLargeOuterSize, Preallocation::kUsed},
233       {Preallocation::kSizeRemaining, Preallocation::kFree},
234   });
235 
236   // Count the blocks. The unallocated ones vary in size, but the allocated ones
237   // should all be the same.
238   size_t free_count = 0;
239   size_t used_count = 0;
240   auto& block_allocator = static_cast<BlockAllocatorType&>(allocator);
241   for (auto* block : block_allocator.blocks()) {
242     if (block->IsFree()) {
243       ++free_count;
244     } else {
245       EXPECT_EQ(block->OuterSize(), kLargeOuterSize);
246       ++used_count;
247     }
248   }
249   EXPECT_EQ(used_count, 3U);
250   EXPECT_EQ(free_count, 4U);
251 }
252 
253 template <typename BlockAllocatorType>
MeasureFragmentation()254 void BlockAllocatorTest<BlockAllocatorType>::MeasureFragmentation() {
255   Allocator& allocator = GetGenericAllocator({
256       {0x020, Preallocation::kFree},
257       {0x040, Preallocation::kUsed},
258       {0x080, Preallocation::kFree},
259       {0x100, Preallocation::kUsed},
260       {0x200, Preallocation::kFree},
261       {Preallocation::kSizeRemaining, Preallocation::kUsed},
262   });
263 
264   auto& block_allocator = static_cast<BlockAllocatorType&>(allocator);
265   constexpr size_t alignment = BlockAllocatorType::BlockType::kAlignment;
266   size_t sum_of_squares = 0;
267   size_t sum = 0;
268   for (const auto block : block_allocator.blocks()) {
269     if (block->IsFree()) {
270       size_t inner_size = block->InnerSize() / alignment;
271       sum_of_squares += inner_size * inner_size;
272       sum += inner_size;
273     }
274   }
275 
276   Fragmentation fragmentation = block_allocator.MeasureFragmentation();
277   EXPECT_EQ(fragmentation.sum_of_squares.hi, 0U);
278   EXPECT_EQ(fragmentation.sum_of_squares.lo, sum_of_squares);
279   EXPECT_EQ(fragmentation.sum, sum);
280 }
281 
282 template <typename BlockAllocatorType>
PoisonPeriodically()283 void BlockAllocatorTest<BlockAllocatorType>::PoisonPeriodically() {
284   // Allocate 8 blocks to prevent every other from being merged when freed.
285   Allocator& allocator = GetGenericAllocator({
286       {kSmallOuterSize, Preallocation::kUsed},
287       {kSmallOuterSize, Preallocation::kUsed},
288       {kSmallOuterSize, Preallocation::kUsed},
289       {kSmallOuterSize, Preallocation::kUsed},
290       {kSmallOuterSize, Preallocation::kUsed},
291       {kSmallOuterSize, Preallocation::kUsed},
292       {kSmallOuterSize, Preallocation::kUsed},
293       {Preallocation::kSizeRemaining, Preallocation::kUsed},
294   });
295   ASSERT_LT(BlockType::kPoisonOffset, kSmallInnerSize);
296 
297   // Since the test poisons blocks, it cannot iterate over the blocks without
298   // crashing. Use `Fetch` instead.
299   for (size_t i = 0; i < 8; ++i) {
300     if (i % 2 != 0) {
301       continue;
302     }
303     auto* bytes = cpp20::bit_cast<std::byte*>(Fetch(i));
304     auto* block = BlockType::FromUsableSpace(bytes);
305     allocator.Deallocate(bytes);
306     EXPECT_TRUE(block->IsFree());
307     EXPECT_TRUE(block->IsValid());
308     bytes[BlockType::kPoisonOffset] = ~bytes[BlockType::kPoisonOffset];
309 
310     if (i == 6) {
311       // The test_config is defined to only detect corruption is on every fourth
312       // freed block. Fix up the block to avoid crashing on teardown.
313       EXPECT_FALSE(block->IsValid());
314       bytes[BlockType::kPoisonOffset] = ~bytes[BlockType::kPoisonOffset];
315     } else {
316       EXPECT_TRUE(block->IsValid());
317     }
318     Store(i, nullptr);
319   }
320 }
321 
322 }  // namespace pw::allocator::test
323