xref: /aosp_15_r20/external/pigweed/pw_allocator/freelist_heap_test.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2020 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 
15 #include "pw_allocator/freelist_heap.h"
16 
17 #include "lib/stdcompat/bit.h"
18 #include "pw_allocator/block/testing.h"
19 #include "pw_bytes/alignment.h"
20 #include "pw_unit_test/framework.h"
21 
22 namespace {
23 
24 // Test fixtures.
25 
26 using ::pw::allocator::FreeListHeapBuffer;
27 using ::pw::allocator::test::GetAlignedOffsetAfter;
28 
29 class FreeListHeapBufferTest : public ::testing::Test {
30  protected:
31   using BlockType = ::pw::allocator::DetailedBlock<>;
32 
33   static constexpr size_t kN = 2048;
34 
35   alignas(BlockType::kAlignment) std::array<std::byte, kN> buffer_;
36 };
37 
38 // Unit tests.
39 
TEST_F(FreeListHeapBufferTest,CanAllocate)40 TEST_F(FreeListHeapBufferTest, CanAllocate) {
41   FreeListHeapBuffer allocator(buffer_);
42 
43   void* ptr = allocator.Allocate(kN / 4);
44   ASSERT_NE(ptr, nullptr);
45 
46   // The returned memory should be within the allocator's memory...
47   EXPECT_GE(ptr, buffer_.data());
48   EXPECT_LT(ptr, buffer_.data() + buffer_.size());
49 
50   // ...and should be usable.
51   std::memset(ptr, 0xff, kN / 4);
52 
53   // All pointers must be freed before the allocator goes out of scope.
54   allocator.Free(ptr);
55 }
56 
TEST_F(FreeListHeapBufferTest,AllocationsDontOverlap)57 TEST_F(FreeListHeapBufferTest, AllocationsDontOverlap) {
58   FreeListHeapBuffer allocator(buffer_);
59 
60   void* ptr1 = allocator.Allocate(kN / 4);
61   ASSERT_NE(ptr1, nullptr);
62   uintptr_t ptr1_start = cpp20::bit_cast<uintptr_t>(ptr1);
63   uintptr_t ptr1_end = ptr1_start + (kN / 4);
64 
65   void* ptr2 = allocator.Allocate(kN / 4);
66   ASSERT_NE(ptr2, nullptr);
67   uintptr_t ptr2_start = cpp20::bit_cast<uintptr_t>(ptr2);
68   uintptr_t ptr2_end = ptr2_start + (kN / 4);
69 
70   if (ptr1 < ptr2) {
71     EXPECT_LT(ptr1_end, ptr2_start);
72   } else {
73     EXPECT_LT(ptr2_end, ptr1_start);
74   }
75 
76   // All pointers must be freed before the allocator goes out of scope.
77   allocator.Free(ptr1);
78   allocator.Free(ptr2);
79 }
80 
TEST_F(FreeListHeapBufferTest,CanFreeAndRealloc)81 TEST_F(FreeListHeapBufferTest, CanFreeAndRealloc) {
82   FreeListHeapBuffer allocator(buffer_);
83 
84   void* ptr1 = allocator.Allocate(kN / 4);
85   allocator.Free(ptr1);
86 
87   // There's not really a nice way to test that Free works, apart from to try
88   // and get that value back again.
89   void* ptr2 = allocator.Allocate(kN / 4);
90 
91   EXPECT_EQ(ptr1, ptr2);
92 
93   // All pointers must be freed before the allocator goes out of scope.
94   allocator.Free(ptr2);
95 }
96 
TEST_F(FreeListHeapBufferTest,ReturnsNullWhenAllocationTooLarge)97 TEST_F(FreeListHeapBufferTest, ReturnsNullWhenAllocationTooLarge) {
98   FreeListHeapBuffer allocator(buffer_);
99 
100   EXPECT_EQ(allocator.Allocate(kN), nullptr);
101 }
102 
TEST_F(FreeListHeapBufferTest,ReturnsNullWhenFull)103 TEST_F(FreeListHeapBufferTest, ReturnsNullWhenFull) {
104   size_t offset = GetAlignedOffsetAfter(
105       buffer_.data(), alignof(std::max_align_t), BlockType::kBlockOverhead);
106   auto buffer = pw::ByteSpan(buffer_).subspan(offset);
107   size_t inner_size = buffer.size() - BlockType::kBlockOverhead;
108 
109   FreeListHeapBuffer allocator(buffer);
110   void* ptr1 = allocator.Allocate(inner_size);
111   ASSERT_NE(ptr1, nullptr);
112 
113   void* ptr2 = allocator.Allocate(1);
114   EXPECT_EQ(ptr2, nullptr);
115 
116   // All pointers must be freed before the allocator goes out of scope.
117   allocator.Free(ptr1);
118 }
119 
TEST_F(FreeListHeapBufferTest,ReturnedPointersAreAligned)120 TEST_F(FreeListHeapBufferTest, ReturnedPointersAreAligned) {
121   FreeListHeapBuffer allocator(buffer_);
122 
123   void* ptr1 = allocator.Allocate(1);
124 
125   // Should be aligned to native pointer alignment
126   uintptr_t ptr1_start = cpp20::bit_cast<uintptr_t>(ptr1);
127   size_t alignment = alignof(void*);
128 
129   EXPECT_EQ(ptr1_start % alignment, static_cast<size_t>(0));
130 
131   void* ptr2 = allocator.Allocate(1);
132   uintptr_t ptr2_start = cpp20::bit_cast<uintptr_t>(ptr2);
133 
134   EXPECT_EQ(ptr2_start % alignment, static_cast<size_t>(0));
135 
136   // All pointers must be freed before the allocator goes out of scope.
137   allocator.Free(ptr1);
138   allocator.Free(ptr2);
139 }
140 
TEST_F(FreeListHeapBufferTest,CanRealloc)141 TEST_F(FreeListHeapBufferTest, CanRealloc) {
142   FreeListHeapBuffer allocator(buffer_);
143 
144   void* ptr1 = allocator.Allocate(kN / 4);
145   ASSERT_NE(ptr1, nullptr);
146 
147   void* ptr2 = allocator.Realloc(ptr1, (kN * 3) / 8);
148   ASSERT_NE(ptr2, nullptr);
149 
150   // All pointers must be freed before the allocator goes out of scope.
151   allocator.Free(ptr2);
152 }
153 
TEST_F(FreeListHeapBufferTest,ReallocHasSameContent)154 TEST_F(FreeListHeapBufferTest, ReallocHasSameContent) {
155   FreeListHeapBuffer allocator(buffer_);
156 
157   size_t val1 = 42;
158   void* ptr1 = allocator.Allocate(sizeof(size_t));
159   ASSERT_NE(ptr1, nullptr);
160   std::memcpy(ptr1, &val1, sizeof(size_t));
161 
162   size_t val2;
163   void* ptr2 = allocator.Realloc(ptr1, sizeof(size_t) * 2);
164   ASSERT_NE(ptr2, nullptr);
165   std::memcpy(&val2, ptr2, sizeof(size_t));
166 
167   // Verify that data inside the allocated and reallocated block are the same.
168   EXPECT_EQ(val1, val2);
169 
170   // All pointers must be freed before the allocator goes out of scope.
171   allocator.Free(ptr2);
172 }
173 
TEST_F(FreeListHeapBufferTest,ReallocSmallerSize)174 TEST_F(FreeListHeapBufferTest, ReallocSmallerSize) {
175   FreeListHeapBuffer allocator(buffer_);
176 
177   void* ptr1 = allocator.Allocate(kN / 4);
178   ASSERT_NE(ptr1, nullptr);
179 
180   // For smaller sizes, Realloc will not shrink the block.
181   void* ptr2 = allocator.Realloc(ptr1, kN / 8);
182   EXPECT_EQ(ptr1, ptr2);
183 
184   // All pointers must be freed before the allocator goes out of scope.
185   allocator.Free(ptr2);
186 }
187 
TEST_F(FreeListHeapBufferTest,ReallocTooLarge)188 TEST_F(FreeListHeapBufferTest, ReallocTooLarge) {
189   FreeListHeapBuffer allocator(buffer_);
190 
191   void* ptr1 = allocator.Allocate(kN / 4);
192   ASSERT_NE(ptr1, nullptr);
193 
194   // Realloc() will not invalidate the original pointer if it fails
195   void* ptr2 = allocator.Realloc(ptr1, kN * 2);
196   EXPECT_EQ(ptr2, nullptr);
197 
198   // All pointers must be freed before the allocator goes out of scope.
199   allocator.Free(ptr1);
200 }
201 
TEST_F(FreeListHeapBufferTest,CanCalloc)202 TEST_F(FreeListHeapBufferTest, CanCalloc) {
203   constexpr size_t kNum = 4;
204   constexpr size_t kSize = 128;
205   constexpr std::array<std::byte, kNum * kSize> kZero = {std::byte(0)};
206 
207   FreeListHeapBuffer allocator(buffer_);
208 
209   void* ptr1 = allocator.Calloc(kNum, kSize);
210   ASSERT_NE(ptr1, nullptr);
211 
212   // Calloc'd content is zero.
213   EXPECT_EQ(std::memcmp(ptr1, kZero.data(), kZero.size()), 0);
214 
215   // All pointers must be freed before the allocator goes out of scope.
216   allocator.Free(ptr1);
217 }
218 
TEST_F(FreeListHeapBufferTest,CanCallocWeirdSize)219 TEST_F(FreeListHeapBufferTest, CanCallocWeirdSize) {
220   constexpr size_t kNum = 4;
221   constexpr size_t kSize = 128;
222   constexpr std::array<std::byte, kNum * kSize> kZero = {std::byte(0)};
223 
224   FreeListHeapBuffer allocator(buffer_);
225 
226   void* ptr1 = allocator.Calloc(kNum, kSize);
227   ASSERT_NE(ptr1, nullptr);
228 
229   // Calloc'd content is zero.
230   EXPECT_EQ(std::memcmp(ptr1, kZero.data(), kZero.size()), 0);
231 
232   // All pointers must be freed before the allocator goes out of scope.
233   allocator.Free(ptr1);
234 }
235 
TEST_F(FreeListHeapBufferTest,CallocTooLarge)236 TEST_F(FreeListHeapBufferTest, CallocTooLarge) {
237   FreeListHeapBuffer allocator(buffer_);
238 
239   void* ptr1 = allocator.Calloc(1, kN + 1);
240   EXPECT_EQ(ptr1, nullptr);
241 
242   // All pointers must be freed before the allocator goes out of scope.
243   allocator.Free(ptr1);
244 }
245 
246 }  // namespace
247