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