xref: /aosp_15_r20/external/pigweed/pw_allocator/public/pw_allocator/testing.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2023 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 <mutex>
18 
19 #include "pw_allocator/allocator.h"
20 #include "pw_allocator/buffer.h"
21 #include "pw_allocator/config.h"
22 #include "pw_allocator/first_fit.h"
23 #include "pw_allocator/metrics.h"
24 #include "pw_allocator/tracking_allocator.h"
25 #include "pw_assert/assert.h"
26 #include "pw_assert/internal/check_impl.h"
27 #include "pw_bytes/span.h"
28 #include "pw_result/result.h"
29 #include "pw_status/status.h"
30 #include "pw_sync/interrupt_spin_lock.h"
31 #include "pw_tokenizer/tokenize.h"
32 #include "pw_unit_test/framework.h"
33 
34 namespace pw::allocator {
35 namespace test {
36 
37 static_assert(PW_ALLOCATOR_STRICT_VALIDATION,
38               "Tests must use a config that enables strict validation");
39 
40 // A token that can be used in tests.
41 constexpr pw::tokenizer::Token kToken = PW_TOKENIZE_STRING("test");
42 
43 /// Free all the blocks reachable by the given block. Useful for test cleanup.
44 template <typename BlockType>
FreeAll(typename BlockType::Range range)45 void FreeAll(typename BlockType::Range range) {
46   BlockType* block = *(range.begin());
47   if (block == nullptr) {
48     return;
49   }
50 
51   // Rewind to the first block.
52   BlockType* prev = block->Prev();
53   while (prev != nullptr) {
54     block = prev;
55     prev = block->Prev();
56   }
57 
58   // Free and merge blocks.
59   while (block != nullptr) {
60     if (!block->IsFree()) {
61       auto result = BlockType::Free(std::move(block));
62       block = result.block();
63     }
64     block = block->Next();
65   }
66 }
67 
68 /// An `AllocatorForTest` that is automatically initialized on construction.
69 template <size_t kBufferSize,
70           typename BlockType_ = FirstFitBlock<uint32_t>,
71           typename MetricsType = internal::AllMetrics>
72 class AllocatorForTest : public Allocator {
73  public:
74   using BlockType = BlockType_;
75   using AllocatorType = FirstFitAllocator<BlockType>;
76 
77   // Since the unbderlying first-fit allocator uses an intrusive free list, all
78   // allocations will be at least this size.
79   static constexpr size_t kMinSize = BlockType::kAlignment;
80 
AllocatorForTest()81   AllocatorForTest()
82       : Allocator(AllocatorType::kCapabilities), tracker_(kToken, *allocator_) {
83     ResetParameters();
84     allocator_->Init(allocator_.as_bytes());
85   }
86 
~AllocatorForTest()87   ~AllocatorForTest() override {
88     FreeAll<BlockType>(blocks());
89     allocator_->Reset();
90   }
91 
blocks()92   typename BlockType::Range blocks() const { return allocator_->blocks(); }
blocks()93   typename BlockType::Range blocks() { return allocator_->blocks(); }
94 
metric_group()95   const metric::Group& metric_group() const { return tracker_.metric_group(); }
metric_group()96   metric::Group& metric_group() { return tracker_.metric_group(); }
97 
metrics()98   const MetricsType& metrics() const { return tracker_.metrics(); }
99 
allocate_size()100   size_t allocate_size() const { return allocate_size_; }
deallocate_ptr()101   void* deallocate_ptr() const { return deallocate_ptr_; }
deallocate_size()102   size_t deallocate_size() const { return deallocate_size_; }
resize_ptr()103   void* resize_ptr() const { return resize_ptr_; }
resize_old_size()104   size_t resize_old_size() const { return resize_old_size_; }
resize_new_size()105   size_t resize_new_size() const { return resize_new_size_; }
106 
107   /// Resets the recorded parameters to an initial state.
ResetParameters()108   void ResetParameters() {
109     allocate_size_ = 0;
110     deallocate_ptr_ = nullptr;
111     deallocate_size_ = 0;
112     resize_ptr_ = nullptr;
113     resize_old_size_ = 0;
114     resize_new_size_ = 0;
115   }
116 
117   /// Allocates all the memory from this object.
Exhaust()118   void Exhaust() {
119     for (auto* block : allocator_->blocks()) {
120       if (block->IsFree()) {
121         auto result = BlockType::AllocLast(std::move(block),
122                                            Layout(block->InnerSize(), 1));
123         PW_ASSERT(result.status() == OkStatus());
124 
125         using Prev = internal::GenericBlockResult::Prev;
126         PW_ASSERT(result.prev() == Prev::kUnchanged);
127 
128         using Next = internal::GenericBlockResult::Next;
129         PW_ASSERT(result.next() == Next::kUnchanged);
130       }
131     }
132   }
133 
134   /// @copydoc BlockAllocator::MeasureFragmentation
MeasureFragmentation()135   Fragmentation MeasureFragmentation() const {
136     return allocator_->MeasureFragmentation();
137   }
138 
139  private:
140   /// @copydoc Allocator::Allocate
DoAllocate(Layout layout)141   void* DoAllocate(Layout layout) override {
142     allocate_size_ = layout.size();
143     void* ptr = tracker_.Allocate(layout);
144     return ptr;
145   }
146 
147   /// @copydoc Allocator::Deallocate
DoDeallocate(void * ptr)148   void DoDeallocate(void* ptr) override {
149     Result<Layout> requested = GetRequestedLayout(tracker_, ptr);
150     deallocate_ptr_ = ptr;
151     deallocate_size_ = requested.ok() ? requested->size() : 0;
152     tracker_.Deallocate(ptr);
153   }
154 
155   /// @copydoc Allocator::Deallocate
DoDeallocate(void * ptr,Layout)156   void DoDeallocate(void* ptr, Layout) override { DoDeallocate(ptr); }
157 
158   /// @copydoc Allocator::Resize
DoResize(void * ptr,size_t new_size)159   bool DoResize(void* ptr, size_t new_size) override {
160     Result<Layout> requested = GetRequestedLayout(tracker_, ptr);
161     resize_ptr_ = ptr;
162     resize_old_size_ = requested.ok() ? requested->size() : 0;
163     resize_new_size_ = new_size;
164     return tracker_.Resize(ptr, new_size);
165   }
166 
167   /// @copydoc Allocator::GetAllocated
DoGetAllocated()168   size_t DoGetAllocated() const override { return tracker_.GetAllocated(); }
169 
170   /// @copydoc Deallocator::GetInfo
DoGetInfo(InfoType info_type,const void * ptr)171   Result<Layout> DoGetInfo(InfoType info_type, const void* ptr) const override {
172     return GetInfo(tracker_, info_type, ptr);
173   }
174 
175   WithBuffer<AllocatorType, kBufferSize> allocator_;
176   TrackingAllocator<MetricsType> tracker_;
177   size_t allocate_size_;
178   void* deallocate_ptr_;
179   size_t deallocate_size_;
180   void* resize_ptr_;
181   size_t resize_old_size_;
182   size_t resize_new_size_;
183 };
184 
185 /// An `AllocatorForTest` that is thread and interrupt-safe and automatically
186 /// initialized on construction.
187 template <size_t kBufferSize,
188           typename BlockType_ = FirstFitBlock<uint32_t>,
189           typename MetricsType = internal::AllMetrics>
190 class SynchronizedAllocatorForTest : public Allocator {
191  private:
192   using BlockType = BlockType_;
193   using Base = AllocatorForTest<kBufferSize, BlockType, MetricsType>;
194 
195   /// @copydoc Allocator::Allocate
DoAllocate(Layout layout)196   void* DoAllocate(Layout layout) override {
197     std::lock_guard lock(lock_);
198     return base_.Allocate(layout);
199   }
200 
201   /// @copydoc Allocator::Deallocate
DoDeallocate(void * ptr)202   void DoDeallocate(void* ptr) override {
203     std::lock_guard lock(lock_);
204     base_.Deallocate(ptr);
205   }
206 
207   /// @copydoc Allocator::Deallocate
DoDeallocate(void * ptr,Layout)208   void DoDeallocate(void* ptr, Layout) override { DoDeallocate(ptr); }
209 
210   /// @copydoc Allocator::Resize
DoResize(void * ptr,size_t new_size)211   bool DoResize(void* ptr, size_t new_size) override {
212     std::lock_guard lock(lock_);
213     return base_.Resize(ptr, new_size);
214   }
215 
216   /// @copydoc Deallocator::GetInfo
DoGetInfo(InfoType info_type,const void * ptr)217   Result<Layout> DoGetInfo(InfoType info_type, const void* ptr) const override {
218     std::lock_guard lock(lock_);
219     return GetInfo(base_, info_type, ptr);
220   }
221 
222   mutable pw::sync::InterruptSpinLock lock_;
223   Base base_;
224 };
225 
226 }  // namespace test
227 }  // namespace pw::allocator
228