1 // Copyright 2016 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <region-alloc/region-alloc.h>
6 #include <stdio.h>
7 #include <unittest/unittest.h>
8 #include <inttypes.h>
9
10 #include <fbl/algorithm.h>
11
12 #include <utility>
13
14 #include "common.h"
15
16 namespace {
17
ralloc_region_pools_test()18 static bool ralloc_region_pools_test() {
19 BEGIN_TEST;
20
21 // Create a default constructed allocator on the stack.
22 RegionAllocator alloc;
23
24 {
25 // Make sure that it refuses to perform any operations because it has no
26 // RegionPool assigned to it yet.
27 RegionAllocator::Region::UPtr tmp;
28 EXPECT_EQ(ZX_ERR_BAD_STATE, alloc.AddRegion({ 0u, 1u }));
29 EXPECT_EQ(ZX_ERR_BAD_STATE, alloc.GetRegion(1, tmp));
30 EXPECT_EQ(ZX_ERR_BAD_STATE, alloc.GetRegion({ 0u, 1u }, tmp));
31 EXPECT_NULL(alloc.GetRegion(1));
32 EXPECT_NULL(alloc.GetRegion({ 0u, 1u }));
33 }
34
35 // Make a region pool to manage bookkeeping allocations.
36 auto pool = RegionAllocator::RegionPool::Create(REGION_POOL_MAX_SIZE);
37 ASSERT_NONNULL(pool);
38
39 // Assign our pool to our allocator, but hold onto the pool for now.
40 ASSERT_EQ(ZX_OK, alloc.SetRegionPool(pool));
41 EXPECT_NONNULL(pool);
42
43 // Create another allocator and transfer ownership of our region pool
44 // reference to it. Then let the allocator go out of scope.
45 {
46 RegionAllocator alloc2(std::move(pool));
47 EXPECT_NULL(pool);
48 }
49 EXPECT_NULL(pool);
50
51 // Add some regions to our allocator.
52 for (size_t i = 0; i < fbl::count_of(GOOD_REGIONS); ++i)
53 EXPECT_EQ(ZX_OK, alloc.AddRegion(GOOD_REGIONS[i]));
54
55 // Make a new pool and try to assign it to the allocator. This should fail
56 // because the allocator is currently using resources from its currently
57 // assigned pool.
58 auto pool2 = RegionAllocator::RegionPool::Create(REGION_POOL_MAX_SIZE);
59 ASSERT_NONNULL(pool2);
60 EXPECT_EQ(ZX_ERR_BAD_STATE, alloc.SetRegionPool(pool2));
61
62 // Add a bunch of adjacent regions to our pool. Try to add so many
63 // that we would normally run out of bookkeeping space. We should not
64 // actually run out, however, because the regions should get merged as they
65 // get added.
66 {
67 ralloc_region_t tmp = { .base = GOOD_MERGE_REGION_BASE,
68 .size = GOOD_MERGE_REGION_SIZE };
69 for (size_t i = 0; i < OOM_RANGE_LIMIT; ++i) {
70 ASSERT_EQ(ZX_OK, alloc.AddRegion(tmp));
71 tmp.base += tmp.size;
72 }
73 }
74
75 // Attempt (and fail) to add some bad regions (regions which overlap,
76 // regions which wrap the address space)
77 for (size_t i = 0; i < fbl::count_of(BAD_REGIONS); ++i)
78 EXPECT_EQ(ZX_ERR_INVALID_ARGS, alloc.AddRegion(BAD_REGIONS[i]));
79
80 // Force the region bookkeeping pool to run out of memory by adding more and
81 // more regions until we eventually run out of room. Make sure that the
82 // regions are not adjacent, or the internal bookkeeping will just merge
83 // them.
84 {
85 size_t i;
86 ralloc_region_t tmp = { .base = BAD_MERGE_REGION_BASE,
87 .size = BAD_MERGE_REGION_SIZE };
88 for (i = 0; i < OOM_RANGE_LIMIT; ++i) {
89 zx_status_t res;
90
91 res = alloc.AddRegion(tmp);
92 if (res != ZX_OK) {
93 EXPECT_EQ(ZX_ERR_NO_MEMORY, res);
94 break;
95 }
96
97 tmp.base += tmp.size + 1;
98 }
99
100 EXPECT_LT(i, OOM_RANGE_LIMIT);
101 }
102
103 // Reset allocator. All of the existing available regions we had previously
104 // added will be returned to the pool.
105 alloc.Reset();
106
107 // Now assign pool2 to the allocator. Now that it is no longer using any
108 // resources, this should succeed.
109 EXPECT_EQ(ZX_OK, alloc.SetRegionPool(std::move(pool2)));
110 EXPECT_NULL(pool2);
111
112 END_TEST;
113 }
114
ralloc_by_size_test()115 static bool ralloc_by_size_test() {
116 BEGIN_TEST;
117
118 // Make a pool and attach it to an allocator. Then add the test regions to it.
119 RegionAllocator alloc(RegionAllocator::RegionPool::Create(REGION_POOL_MAX_SIZE));
120
121 for (size_t i = 0; i < fbl::count_of(ALLOC_BY_SIZE_REGIONS); ++i)
122 ASSERT_EQ(ZX_OK, alloc.AddRegion(ALLOC_BY_SIZE_REGIONS[i]));
123
124 // Run the alloc by size tests. Hold onto the regions it allocates so they
125 // don't automatically get returned to the pool.
126 RegionAllocator::Region::UPtr regions[fbl::count_of(ALLOC_BY_SIZE_TESTS)];
127
128 for (size_t i = 0; i < fbl::count_of(ALLOC_BY_SIZE_TESTS); ++i) {
129 const alloc_by_size_alloc_test_t* TEST = ALLOC_BY_SIZE_TESTS + i;
130 zx_status_t res = alloc.GetRegion(TEST->size, TEST->align, regions[i]);
131
132 // Make sure we get the test result we were expecting.
133 EXPECT_EQ(TEST->res, res);
134
135 // If the allocation claimed to succeed, we should have gotten
136 // back a non-null region. Otherwise, we should have gotten a
137 // null region back.
138 if (res == ZX_OK) {
139 ASSERT_NONNULL(regions[i]);
140 } else {
141 EXPECT_NULL(regions[i]);
142 }
143
144 // If the allocation succeeded, and we expected it to succeed,
145 // the allocation should have come from the test region we
146 // expect and be aligned in the way we asked.
147 if ((res == ZX_OK) && (TEST->res == ZX_OK)) {
148 ASSERT_LT(TEST->region, fbl::count_of(ALLOC_BY_SIZE_TESTS));
149 EXPECT_TRUE(region_contains_region(ALLOC_BY_SIZE_REGIONS + TEST->region,
150 regions[i].get()));
151 EXPECT_EQ(0u, regions[i]->base & (TEST->align - 1));
152 }
153
154 }
155
156 // No need for any explicit cleanup. Our region references will go out of
157 // scope first and be returned to the allocator. Then the allocator will
158 // clean up, and release its bookkeeping pool reference in the process.
159
160 END_TEST;
161 }
162
ralloc_specific_test()163 static bool ralloc_specific_test() {
164 BEGIN_TEST;
165
166 // Make a pool and attach it to an allocator. Then add the test regions to it.
167 RegionAllocator alloc(RegionAllocator::RegionPool::Create(REGION_POOL_MAX_SIZE));
168
169 for (size_t i = 0; i < fbl::count_of(ALLOC_SPECIFIC_REGIONS); ++i)
170 ASSERT_EQ(ZX_OK, alloc.AddRegion(ALLOC_SPECIFIC_REGIONS[i]));
171
172 // Run the alloc specific tests. Hold onto the regions it allocates so they
173 // don't automatically get returned to the pool.
174 RegionAllocator::Region::UPtr regions[fbl::count_of(ALLOC_SPECIFIC_TESTS)];
175
176 for (size_t i = 0; i < fbl::count_of(ALLOC_SPECIFIC_TESTS); ++i) {
177 const alloc_specific_alloc_test_t* TEST = ALLOC_SPECIFIC_TESTS + i;
178 zx_status_t res = alloc.GetRegion(TEST->req, regions[i]);
179
180 // Make sure we get the test result we were expecting.
181 EXPECT_EQ(TEST->res, res);
182
183 // If the allocation claimed to succeed, we should have gotten back a
184 // non-null region which exactly matches our requested region.
185 if (res == ZX_OK) {
186 ASSERT_NONNULL(regions[i]);
187 EXPECT_EQ(TEST->req.base, regions[i]->base);
188 EXPECT_EQ(TEST->req.size, regions[i]->size);
189 } else {
190 EXPECT_NULL(regions[i]);
191 }
192 }
193
194 // No need for any explicit cleanup. Our region references will go out of
195 // scope first and be returned to the allocator. Then the allocator will
196 // clean up, and release its bookkeeping pool reference in the process.
197
198 END_TEST;
199 }
200
ralloc_add_overlap_test()201 static bool ralloc_add_overlap_test() {
202 BEGIN_TEST;
203
204 // Make a pool and attach it to an allocator. Then add the test regions to it.
205 RegionAllocator alloc(RegionAllocator::RegionPool::Create(REGION_POOL_MAX_SIZE));
206
207 // Add each of the regions specified by the test and check the expected results.
208 for (size_t i = 0; i < fbl::count_of(ADD_OVERLAP_TESTS); ++i) {
209 const alloc_add_overlap_test_t* TEST = ADD_OVERLAP_TESTS + i;
210
211 zx_status_t res = alloc.AddRegion(TEST->reg, TEST->ovl);
212
213 EXPECT_EQ(TEST->res, res);
214 EXPECT_EQ(TEST->cnt, alloc.AvailableRegionCount());
215 }
216
217 END_TEST;
218 }
219
ralloc_subtract_test()220 static bool ralloc_subtract_test() {
221 BEGIN_TEST;
222
223 // Make a pool and attach it to an allocator. Then add the test regions to it.
224 RegionAllocator alloc(RegionAllocator::RegionPool::Create(REGION_POOL_MAX_SIZE));
225
226 // Run the test sequence, adding and subtracting regions and verifying the results.
227 for (size_t i = 0; i < fbl::count_of(SUBTRACT_TESTS); ++i) {
228 const alloc_subtract_test_t* TEST = SUBTRACT_TESTS + i;
229
230 zx_status_t res;
231 if (TEST->add)
232 res = alloc.AddRegion(TEST->reg);
233 else
234 res = alloc.SubtractRegion(TEST->reg, TEST->incomplete);
235
236 EXPECT_EQ(TEST->res ? ZX_OK : ZX_ERR_INVALID_ARGS, res);
237 EXPECT_EQ(TEST->cnt, alloc.AvailableRegionCount());
238 }
239
240 END_TEST;
241 }
242
ralloc_alloc_walk_test()243 static bool ralloc_alloc_walk_test() {
244 BEGIN_TEST;
245
246 const ralloc_region_t test_regions[] = {
247 { .base = 0x00000000, .size = 1 << 20 },
248 { .base = 0x10000000, .size = 1 << 20 },
249 { .base = 0x20000000, .size = 1 << 20 },
250 { .base = 0x30000000, .size = 1 << 20 },
251 { .base = 0x40000000, .size = 1 << 20 },
252 { .base = 0x50000000, .size = 1 << 20 },
253 { .base = 0x60000000, .size = 1 << 20 },
254 { .base = 0x70000000, .size = 1 << 20 },
255 { .base = 0x80000000, .size = 1 << 20 },
256 { .base = 0x90000000, .size = 1 << 20 },
257 };
258 constexpr size_t r_cnt = fbl::count_of(test_regions);
259
260 RegionAllocator alloc(RegionAllocator::RegionPool::Create(REGION_POOL_MAX_SIZE));
261 EXPECT_EQ(ZX_OK, alloc.AddRegion({ .base = 0, .size = UINT64_MAX}));
262
263 // Pull each region defined above out of the allocator and stash their UPtrs
264 // for the time being. Then the lambda can walk the allocated regions and
265 // verify that they are in-order and match the expected values.
266 RegionAllocator::Region::UPtr r[r_cnt];
267 for (unsigned i = 0; i < r_cnt; i++) {
268 EXPECT_EQ(ZX_OK, alloc.GetRegion(test_regions[i], r[i]));
269 }
270
271 uint8_t pos = 0;
272 uint64_t end = 0;
273 auto f = [&](const ralloc_region_t* r) -> bool {
274 ASSERT_EQ(r->base, test_regions[pos].base);
275 ASSERT_EQ(r->size, test_regions[pos].size);
276 pos++;
277
278 // attempt to exit early if end is set to a value > 0
279 return (end) ? (pos != end) : true;
280 };
281
282 alloc.WalkAllocatedRegions(f);
283 ASSERT_EQ(r_cnt, pos);
284
285 // Test that exiting early works, no matter where we are in the region list.
286 // Every time the function is called we increment the counter and then at
287 // the end ensure we've only been called as many times as expected, within
288 // the bounds of [1, r_cnt].
289 for (size_t cnt = 0; cnt < 1024; cnt++) {
290 pos = 0;
291 end = (rand() % r_cnt) + 1;
292 alloc.WalkAllocatedRegions(f);
293 ASSERT_EQ(pos, end);
294 }
295
296 END_TEST;
297 }
298
299 } //namespace
300
301 BEGIN_TEST_CASE(ralloc_tests)
302 RUN_NAMED_TEST("Region Pools", ralloc_region_pools_test)
303 RUN_NAMED_TEST("Alloc by size", ralloc_by_size_test)
304 RUN_NAMED_TEST("Alloc specific", ralloc_specific_test)
305 RUN_NAMED_TEST("Add/Overlap", ralloc_add_overlap_test)
306 RUN_NAMED_TEST("Subtract", ralloc_subtract_test)
307 RUN_NAMED_TEST("Allocated Walk", ralloc_alloc_walk_test)
308 END_TEST_CASE(ralloc_tests)
309