1//////////////////////////////////////////////////////////////////////////////
2//
3// (C) Copyright Ion Gaztanaga 2015-2015. Distributed under the Boost
4// Software License, Version 1.0. (See accompanying file
5// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6//
7// See http://www.boost.org/libs/container for documentation.
8//
9//////////////////////////////////////////////////////////////////////////////
10
11#include <boost/container/pmr/global_resource.hpp>
12#include <boost/core/lightweight_test.hpp>
13
14#include <boost/intrusive/detail/math.hpp>
15
16#include "derived_from_memory_resource.hpp"
17#include "memory_resource_logger.hpp"
18
19using namespace boost::container::pmr;
20
21template<class PoolResource>
22struct derived_from_pool_resource
23 : public PoolResource
24{
25 derived_from_pool_resource(const pool_options& opts, memory_resource* upstream)
26 : PoolResource(opts, upstream)
27 {}
28
29 explicit derived_from_pool_resource(memory_resource *p)
30 : PoolResource(p)
31 {}
32
33 explicit derived_from_pool_resource(const pool_options &opts)
34 : PoolResource(opts)
35 {}
36
37 derived_from_pool_resource()
38 : PoolResource()
39 {}
40
41 using PoolResource::do_allocate;
42 using PoolResource::do_deallocate;
43 using PoolResource::do_is_equal;
44};
45
46template<class PoolResource>
47void test_default_constructor()
48{
49 //With default options/resource
50 {
51 derived_from_memory_resource dmr;
52 dmr.reset();
53 PoolResource m;
54 //test postconditions
55 BOOST_TEST(m.upstream_resource() == get_default_resource());
56 BOOST_TEST(m.options().max_blocks_per_chunk == pool_options_default_max_blocks_per_chunk);
57 BOOST_TEST(m.options().largest_required_pool_block == pool_options_default_largest_required_pool_block);
58 //test it does not allocate any memory
59 BOOST_TEST(dmr.do_allocate_called == false);
60 }
61}
62
63template<class PoolResource>
64void test_upstream_constructor()
65{
66 //With a resource, default options
67 {
68 derived_from_memory_resource dmr;
69 dmr.reset();
70 PoolResource m(&dmr);
71 //test postconditions
72 BOOST_TEST(m.upstream_resource() == &dmr);
73 BOOST_TEST(m.options().max_blocks_per_chunk == pool_options_default_max_blocks_per_chunk);
74 BOOST_TEST(m.options().largest_required_pool_block == pool_options_default_largest_required_pool_block);
75 //test it does not allocate any memory
76 BOOST_TEST(dmr.do_allocate_called == false);
77 }
78}
79
80template<class PoolResource>
81void test_options_constructor()
82{
83 //Default options
84 {
85 memory_resource_logger mrl;
86 BOOST_TEST(mrl.m_info.size() == 0u);
87 set_default_resource(&mrl);
88 pool_options opts;
89 PoolResource m(opts);
90 //test postconditions
91 BOOST_TEST(m.upstream_resource() == get_default_resource());
92 BOOST_TEST(m.options().max_blocks_per_chunk == pool_options_default_max_blocks_per_chunk);
93 BOOST_TEST(m.options().largest_required_pool_block == pool_options_default_largest_required_pool_block);
94 //test it does not allocate any memory
95 BOOST_TEST(mrl.m_info.size() == 0u);
96 }
97 //Too large option values
98 {
99 memory_resource_logger mrl;
100 BOOST_TEST(mrl.m_info.size() == 0u);
101 set_default_resource(&mrl);
102 pool_options opts;
103 opts.max_blocks_per_chunk = pool_options_default_max_blocks_per_chunk+1;
104 opts.largest_required_pool_block = pool_options_default_largest_required_pool_block+1;
105 PoolResource m(opts);
106 //test postconditions
107 BOOST_TEST(m.upstream_resource() == get_default_resource());
108 BOOST_TEST(m.options().max_blocks_per_chunk == pool_options_default_max_blocks_per_chunk);
109 BOOST_TEST(m.options().largest_required_pool_block == pool_options_default_largest_required_pool_block);
110 //test it does not allocate any memory
111 BOOST_TEST(mrl.m_info.size() == 0u);
112 }
113 //Too small option values
114 {
115 memory_resource_logger mrl;
116 BOOST_TEST(mrl.m_info.size() == 0u);
117 set_default_resource(&mrl);
118 pool_options opts;
119 opts.largest_required_pool_block = pool_options_minimum_largest_required_pool_block-1u;
120 PoolResource m(opts);
121 //test postconditions
122 BOOST_TEST(m.upstream_resource() == get_default_resource());
123 BOOST_TEST(m.options().max_blocks_per_chunk == pool_options_default_max_blocks_per_chunk);
124 BOOST_TEST(m.options().largest_required_pool_block == pool_options_minimum_largest_required_pool_block);
125 //test it does not allocate any memory
126 BOOST_TEST(mrl.m_info.size() == 0u);
127 }
128 //In range option values
129 {
130 memory_resource_logger mrl;
131 BOOST_TEST(mrl.m_info.size() == 0u);
132 set_default_resource(&mrl);
133 pool_options opts;
134 opts.max_blocks_per_chunk = pool_options_default_max_blocks_per_chunk;
135 opts.largest_required_pool_block = pool_options_minimum_largest_required_pool_block;
136 PoolResource m(opts);
137 //test postconditions
138 BOOST_TEST(m.upstream_resource() == get_default_resource());
139 BOOST_TEST(m.options().max_blocks_per_chunk == pool_options_default_max_blocks_per_chunk);
140 BOOST_TEST(m.options().largest_required_pool_block == pool_options_minimum_largest_required_pool_block);
141 //test it does not allocate any memory
142 BOOST_TEST(mrl.m_info.size() == 0u);
143 }
144}
145
146template<class PoolResource>
147void test_options_upstream_constructor()
148{
149 //Default options
150 {
151 derived_from_memory_resource dmr;
152 dmr.reset();
153 pool_options opts;
154 PoolResource m(opts, &dmr);
155 //test postconditions
156 BOOST_TEST(m.upstream_resource() == &dmr);
157 BOOST_TEST(m.options().max_blocks_per_chunk == pool_options_default_max_blocks_per_chunk);
158 BOOST_TEST(m.options().largest_required_pool_block == pool_options_default_largest_required_pool_block);
159 //test it does not allocate any memory
160 BOOST_TEST(dmr.do_allocate_called == false);
161 }
162 //Too large option values
163 {
164 derived_from_memory_resource dmr;
165 dmr.reset();
166 pool_options opts;
167 opts.max_blocks_per_chunk = pool_options_default_max_blocks_per_chunk+1;
168 opts.largest_required_pool_block = pool_options_default_largest_required_pool_block+1;
169 PoolResource m(opts, &dmr);
170 //test postconditions
171 BOOST_TEST(m.upstream_resource() == &dmr);
172 BOOST_TEST(m.options().max_blocks_per_chunk == pool_options_default_max_blocks_per_chunk);
173 BOOST_TEST(m.options().largest_required_pool_block == pool_options_default_largest_required_pool_block);
174 //test it does not allocate any memory
175 BOOST_TEST(dmr.do_allocate_called == false);
176 }
177 //Too small option values
178 {
179 derived_from_memory_resource dmr;
180 dmr.reset();
181 pool_options opts;
182 opts.largest_required_pool_block = pool_options_minimum_largest_required_pool_block-1u;
183 PoolResource m(opts, &dmr);
184 //test postconditions
185 BOOST_TEST(m.upstream_resource() == &dmr);
186 BOOST_TEST(m.options().max_blocks_per_chunk == pool_options_default_max_blocks_per_chunk);
187 BOOST_TEST(m.options().largest_required_pool_block == pool_options_minimum_largest_required_pool_block);
188 //test it does not allocate any memory
189 BOOST_TEST(dmr.do_allocate_called == false);
190 }
191 //In range option values
192 {
193 derived_from_memory_resource dmr;
194 dmr.reset();
195 pool_options opts;
196 opts.max_blocks_per_chunk = pool_options_default_max_blocks_per_chunk;
197 opts.largest_required_pool_block = pool_options_minimum_largest_required_pool_block;
198 PoolResource m(opts, &dmr);
199 //test postconditions
200 BOOST_TEST(m.upstream_resource() == &dmr);
201 //max blocks is unchanged in this implementation
202 BOOST_TEST(m.options().max_blocks_per_chunk == pool_options_default_max_blocks_per_chunk);
203 //largest block is rounded to pow2
204 BOOST_TEST(m.options().largest_required_pool_block == bi::detail::ceil_pow2(opts.largest_required_pool_block));
205 //test it does not allocate any memory
206 BOOST_TEST(dmr.do_allocate_called == false);
207 }
208}
209
210template<class PoolResource>
211void test_options()
212{
213 //In range option values
214 {
215 derived_from_memory_resource dmr;
216 dmr.reset();
217 pool_options opts;
218 opts.max_blocks_per_chunk = pool_options_default_max_blocks_per_chunk/2u;
219 opts.largest_required_pool_block = (pool_options_default_largest_required_pool_block
220 - pool_options_minimum_largest_required_pool_block) | std::size_t(1); //guaranteed to be non power of 2.
221 PoolResource m(opts, &dmr);
222 //test postconditions
223 BOOST_TEST(m.upstream_resource() == &dmr);
224 //max blocks is unchanged in this implementation
225 BOOST_TEST(m.options().max_blocks_per_chunk == opts.max_blocks_per_chunk);
226 //largest block is rounded to pow2
227 BOOST_TEST(m.options().largest_required_pool_block == bi::detail::ceil_pow2(opts.largest_required_pool_block));
228 //test it does not allocate any memory
229 BOOST_TEST(dmr.do_allocate_called == false);
230 }
231}
232
233template<class PoolResource>
234void test_do_allocate_deallocate()
235{
236 memory_resource_logger mrl;
237 {
238 derived_from_pool_resource<PoolResource> dmbr(&mrl);
239 {
240 //First block from pool 0
241 dmbr.do_allocate(1, 1);
242 //It should allocate the pool array plus an initial block
243 BOOST_TEST(mrl.m_info.size() == 2u);
244 //Second block from pool 0
245 dmbr.do_allocate(1, 1);
246 //It should allocate again (with 2 chunks per block)
247 BOOST_TEST(mrl.m_info.size() == 3u);
248 //Third block from pool 0
249 dmbr.do_allocate(1, 1);
250 //It should NOT allocate again (previous was a 2 block chunk)
251 BOOST_TEST(mrl.m_info.size() == 3u);
252 }
253 }
254 BOOST_TEST(mrl.m_mismatches == 0u);
255 BOOST_TEST(mrl.m_info.size() == 0u);
256
257 //Allocate and deallocate from the same chunk to test block caching
258 {
259 derived_from_pool_resource<PoolResource> dmbr(&mrl);
260 {
261 //First block from pool 0
262 void *p = dmbr.do_allocate(1, 1);
263 //It should allocate the pool array plus an initial block
264 BOOST_TEST(mrl.m_info.size() == 2u);
265 //No cached, as initial blocks per chunk is 1
266 BOOST_TEST(dmbr.pool_cached_blocks(0u) == 0u);
267 //Deallocate and allocate again
268 dmbr.do_deallocate(p, 1, 1);
269 //Cached
270 BOOST_TEST(dmbr.pool_cached_blocks(0u) == 1u);
271 p = dmbr.do_allocate(1, 1);
272 //Reused
273 BOOST_TEST(dmbr.pool_cached_blocks(0u) == 0u);
274 //It should have NOT allocated (block reuse)
275 BOOST_TEST(mrl.m_info.size() == 2u);
276
277 //Allocate again 2 times (a 2 block chunk is exhausted)
278 void *p2 = dmbr.do_allocate(1, 1);
279 //1 left cached
280 BOOST_TEST(dmbr.pool_cached_blocks(0u) == 1u);
281 void *p3 = dmbr.do_allocate(1, 1);
282 //Cache exhausted
283 BOOST_TEST(dmbr.pool_cached_blocks(0u) == 0u);
284 //Single chunk allocation happened
285 BOOST_TEST(mrl.m_info.size() == 3u);
286
287 //Now deallocate all (no memory is freed, all cached)
288 dmbr.do_deallocate(p2, 1, 1);
289 dmbr.do_deallocate(p3, 1, 1);
290 dmbr.do_deallocate(p, 1, 1);
291 BOOST_TEST(dmbr.pool_cached_blocks(0u) == 3u);
292 BOOST_TEST(mrl.m_info.size() == 3u);
293 }
294 }
295 BOOST_TEST(mrl.m_mismatches == 0u);
296 BOOST_TEST(mrl.m_info.size() == 0u);
297
298 //Now test max block per chunk
299 {
300 pool_options opts;
301 //so after max_blocks_per_chunk*2-1 allocations, all new chunks must hold max_blocks_per_chunk blocks
302 opts.max_blocks_per_chunk = 32u;
303 derived_from_pool_resource<PoolResource> dmbr(opts, &mrl);
304 {
305 std::size_t loops = opts.max_blocks_per_chunk*2-1u;
306 while(loops--){
307 dmbr.do_allocate(1, 1);
308 }
309 //pool array + log2(max_blocks_per_chunk)+1 chunks (sizes [1, 2, 4, ...])
310 const std::size_t num_chunks = bi::detail::floor_log2(n: opts.max_blocks_per_chunk)+1u;
311 BOOST_TEST(mrl.m_info.size() == 1u + num_chunks);
312 //Next allocation should allocate max_blocks_per_chunk blocks in a chunk so max_blocks_per_chunk-1 should remain free
313 dmbr.do_allocate(1, 1);
314 BOOST_TEST(mrl.m_info.size() == 1u + num_chunks + 1u);
315 BOOST_TEST(dmbr.pool_cached_blocks(0u) == (opts.max_blocks_per_chunk-1u));
316 //Exhaust the chunk and allocate a new one, test max_blocks_per_chunk is not passed again
317 loops = opts.max_blocks_per_chunk;
318 while(loops--){
319 dmbr.do_allocate(1, 1);
320 }
321 BOOST_TEST(mrl.m_info.size() == 1u + num_chunks + 2u);
322 BOOST_TEST(dmbr.pool_cached_blocks(0u) == (opts.max_blocks_per_chunk-1u));
323 }
324 }
325 BOOST_TEST(mrl.m_mismatches == 0u);
326 BOOST_TEST(mrl.m_info.size() == 0u);
327
328 //Now test max block per chunk
329 {
330 pool_options opts;
331 //so after max_blocks_per_chunk*2-1 allocations, all new chunks must hold max_blocks_per_chunk blocks
332 opts.max_blocks_per_chunk = 32u;
333 derived_from_pool_resource<PoolResource> dmbr(opts, &mrl);
334 {
335 std::size_t loops = opts.max_blocks_per_chunk*2-1u;
336 while(loops--){
337 dmbr.do_allocate(1, 1);
338 }
339 //pool array + log2(max_blocks_per_chunk)+1 chunks (sizes [1, 2, 4, ...])
340 BOOST_TEST(dmbr.pool_next_blocks_per_chunk(0u) == opts.max_blocks_per_chunk);
341 const std::size_t num_chunks = bi::detail::floor_log2(n: opts.max_blocks_per_chunk)+1u;
342 BOOST_TEST(mrl.m_info.size() == 1u + num_chunks);
343 //Next allocation should allocate max_blocks_per_chunk blocks in a chunk so max_blocks_per_chunk-1 should remain free
344 dmbr.do_allocate(1, 1);
345 BOOST_TEST(dmbr.pool_next_blocks_per_chunk(0u) == opts.max_blocks_per_chunk);
346 BOOST_TEST(mrl.m_info.size() == 1u + num_chunks + 1u);
347 BOOST_TEST(dmbr.pool_cached_blocks(0u) == (opts.max_blocks_per_chunk-1u));
348 }
349 }
350 BOOST_TEST(mrl.m_mismatches == 0u);
351 BOOST_TEST(mrl.m_info.size() == 0u);
352
353 //Now test different pool sizes
354 {
355 pool_options opts;
356 //so after max_blocks_per_chunk*2-1 allocations, all new chunks must hold max_blocks_per_chunk blocks
357 opts.max_blocks_per_chunk = 1u;
358 derived_from_pool_resource<PoolResource> dmbr(opts, &mrl);
359 const pool_options &final_opts = dmbr.options();
360
361 //Force pool creation
362 dmbr.do_deallocate(dmbr.do_allocate(1, 1), 1, 1);
363 //pool array plus first pool's chunk allocation
364 BOOST_TEST(mrl.m_info.size() == 2u);
365 //pool count must be:
366 // log2(the maximum block) - log2(the minimum block) + 1. Example if minimum block is 8, and maximum 32:
367 // log(32) - log2(8) + 1u = 3 pools (block sizes: 8, 16, and 32)
368 const std::size_t minimum_size = dmbr.pool_block(0u);
369 const std::size_t maximum_size = final_opts.largest_required_pool_block;
370 BOOST_TEST(dmbr.pool_count() == (1u + bi::detail::floor_log2(maximum_size) - bi::detail::floor_log2(minimum_size)));
371 for(std::size_t i = 0, s = minimum_size, max = dmbr.pool_count(); i != max; ++i, s*=2){
372 //Except in the first pool, each cache should be empty
373 BOOST_TEST(dmbr.pool_cached_blocks(i) == std::size_t(i == 0));
374 dmbr.do_deallocate(dmbr.do_allocate(s/2+1, 1), s/2+1, 1);
375 dmbr.do_deallocate(dmbr.do_allocate(s-1, 1), s-1, 1);
376 dmbr.do_deallocate(dmbr.do_allocate(s, 1), s, 1);
377 //pool array plus each previous chunk allocation
378 BOOST_TEST(mrl.m_info.size() == (1u + i + 1u));
379 //as we limited max_blocks_per_chunk to 1, no cached blocks should be available except one
380 BOOST_TEST(dmbr.pool_cached_blocks(i) == 1u);
381 }
382 //Now test out of maximum values, which should go directly to upstream
383 //it should be directly deallocated.
384 void *p = dmbr.do_allocate(maximum_size+1, 1);
385 BOOST_TEST(mrl.m_info.size() == (1u + dmbr.pool_count() + 1u));
386 dmbr.do_deallocate(p, maximum_size+1, 1);
387 BOOST_TEST(mrl.m_info.size() == (1u + dmbr.pool_count()));
388 }
389 BOOST_TEST(mrl.m_mismatches == 0u);
390 BOOST_TEST(mrl.m_info.size() == 0u);
391}
392
393template<class PoolResource>
394void test_do_is_equal()
395{
396 //`this == dynamic_cast<const PoolResource*>(&other)`.
397 memory_resource_logger mrl;
398 derived_from_pool_resource<PoolResource> dmbr(&mrl);
399 derived_from_pool_resource<PoolResource> dmbr2(&mrl);
400 BOOST_TEST(true == dmbr.do_is_equal(dmbr));
401 BOOST_TEST(false == dmbr.do_is_equal(dmbr2));
402 //A different type should be always different
403 derived_from_memory_resource dmr;
404 BOOST_TEST(false == dmbr.do_is_equal(dmr));
405}
406
407template<class PoolResource>
408void test_release()
409{
410 memory_resource_logger mrl;
411 {
412 pool_options opts;
413 //so after max_blocks_per_chunk*2-1 allocations, all new chunks must hold max_blocks_per_chunk blocks
414 opts.max_blocks_per_chunk = 4u;
415 derived_from_pool_resource<PoolResource> dmbr(opts, &mrl);
416 const pool_options &final_opts = dmbr.options();
417 const std::size_t minimum_size = dmbr.pool_block(0u);
418 const std::size_t maximum_size = final_opts.largest_required_pool_block;
419 const std::size_t pool_count = 1u + bi::detail::floor_log2(n: maximum_size) - bi::detail::floor_log2(n: minimum_size);
420
421 std::size_t expected_memory_allocs = 0;
422 for(std::size_t i = 0, imax = pool_count, s = minimum_size; i != imax; s*=2, ++i){
423 for(std::size_t j = 0, j_max = opts.max_blocks_per_chunk*2u-1u; j != j_max; ++j){
424 dmbr.do_allocate(s, 1);
425 }
426 //One due to the pool array, and for each pool, log2(max_blocks_per_chunk)+1 allocations
427 expected_memory_allocs = 1 + (bid::floor_log2(n: opts.max_blocks_per_chunk) + 1u)*(i+1);
428 //pool array plus each previous chunk allocation
429 BOOST_TEST(mrl.m_info.size() == expected_memory_allocs);
430 }
431 //Now with out-of-pool sizes
432 for(std::size_t j = 0, j_max = opts.max_blocks_per_chunk*2u-1u; j != j_max; ++j){
433 dmbr.do_allocate(maximum_size+1, 1);
434 BOOST_TEST(mrl.m_info.size() == ++expected_memory_allocs);
435 }
436 //Now release memory and check all memory allocated through do_allocate was deallocated to upstream
437 dmbr.release();
438 BOOST_TEST(mrl.m_info.size() == 1u);
439 }
440 BOOST_TEST(mrl.m_mismatches == 0u);
441 BOOST_TEST(mrl.m_info.size() == 0u);
442}
443
444template<class PoolResource>
445void test_destructor()
446{
447 memory_resource_logger mrl;
448 {
449 pool_options opts;
450 //so after max_blocks_per_chunk*2-1 allocations, all new chunks must hold max_blocks_per_chunk blocks
451 opts.max_blocks_per_chunk = 4u;
452 derived_from_pool_resource<PoolResource> dmbr(opts, &mrl);
453 const pool_options &final_opts = dmbr.options();
454 const std::size_t minimum_size = dmbr.pool_block(0u);
455 const std::size_t maximum_size = final_opts.largest_required_pool_block;
456 const std::size_t pool_count = 1u + bi::detail::floor_log2(n: maximum_size) - bi::detail::floor_log2(n: minimum_size);
457
458 std::size_t expected_memory_allocs = 0;
459 for(std::size_t i = 0, imax = pool_count, s = minimum_size; i != imax; s*=2, ++i){
460 for(std::size_t j = 0, j_max = opts.max_blocks_per_chunk*2u-1u; j != j_max; ++j){
461 dmbr.do_allocate(s, 1);
462 }
463 //One due to the pool array, and for each pool, log2(max_blocks_per_chunk)+1 allocations
464 expected_memory_allocs = 1 + (bid::floor_log2(n: opts.max_blocks_per_chunk) + 1u)*(i+1);
465 //pool array plus each previous chunk allocation
466 BOOST_TEST(mrl.m_info.size() == expected_memory_allocs);
467 }
468 //Now with out-of-pool sizes
469 for(std::size_t j = 0, j_max = opts.max_blocks_per_chunk*2u-1u; j != j_max; ++j){
470 dmbr.do_allocate(maximum_size+1, 1);
471 BOOST_TEST(mrl.m_info.size() == ++expected_memory_allocs);
472 }
473 //Don't release, all memory, including internal allocations, should be automatically
474 //released after the destructor is run
475 }
476 BOOST_TEST(mrl.m_mismatches == 0u);
477 BOOST_TEST(mrl.m_info.size() == 0u);
478}
479
480
481template<class PoolResource>
482void test_pool_resource()
483{
484 test_options_upstream_constructor<PoolResource>();
485 test_default_constructor<PoolResource>();
486 test_upstream_constructor<PoolResource>();
487 test_options_constructor<PoolResource>();
488 test_options<PoolResource>();
489 test_do_allocate_deallocate<PoolResource>();
490 test_do_is_equal<PoolResource>();
491 test_release<PoolResource>();
492 test_destructor<PoolResource>();
493}
494

source code of boost/libs/container/test/pool_resource_test.hpp