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/monotonic_buffer_resource.hpp>
12 #include <boost/container/pmr/global_resource.hpp>
13 #include <boost/core/lightweight_test.hpp>
14 #include "derived_from_memory_resource.hpp"
15 #include "memory_resource_logger.hpp"
16 
17 using namespace boost::container::pmr;
18 
19 static const std::size_t AllocCount = 32u;
20 
21 namespace test_block_chain{
22 
23 //explicit block_slist(memory_resource &upstream_rsrc)
test_constructor()24 void test_constructor()
25 {
26    memory_resource_logger mrl;
27    block_slist bc(mrl);
28    //Resource stored
29    BOOST_TEST(&bc.upstream_resource() == &mrl);
30    //No allocation performed
31    BOOST_TEST(mrl.m_info.size() == 0u);
32 }
33 
34 //void *allocate(std::size_t size)
test_allocate()35 void test_allocate()
36 {
37    memory_resource_logger mrl;
38    block_slist bc(mrl);
39 
40    for(unsigned i = 0; i != unsigned(AllocCount); ++i){
41       //Allocate and trace data
42       const std::size_t alloc    = i+1;
43       char *const addr           = (char*)bc.allocate(alloc);
44       //Should have allocated a new entry
45       BOOST_TEST(mrl.m_info.size() == (i+1));
46       //Requested size must be bigger to include metadata
47       BOOST_TEST(mrl.m_info[i].bytes > alloc);
48       BOOST_TEST(mrl.m_info[i].alignment == memory_resource::max_align);
49       //Returned address should be between the allocated buffer
50       BOOST_TEST(mrl.m_info[i].address < addr);
51       BOOST_TEST(addr < (mrl.m_info[i].address + mrl.m_info[i].bytes));
52       //Allocate size should include all requested size
53       BOOST_TEST((addr + alloc) <= (mrl.m_info[i].address + mrl.m_info[i].bytes));
54       //Allocation must be max-aligned
55       BOOST_TEST((std::size_t(addr) % memory_resource::max_align) == 0);
56    }
57 }
58 
59 //void release() BOOST_NOEXCEPT
test_release()60 void test_release()
61 {
62    memory_resource_logger mrl;
63    block_slist bc(mrl);
64 
65    //Allocate and trace data
66    char *bufs[AllocCount];
67    for(unsigned i = 0; i != unsigned(AllocCount); ++i){
68       bufs[i]                    = (char*)bc.allocate(i+1);
69    }
70    (void)bufs;
71    //Should have allocated a new entry
72    BOOST_TEST(mrl.m_info.size() == AllocCount);
73 
74    //Now release and check all allocations match deallocations
75    bc.release();
76    BOOST_TEST(mrl.m_mismatches == 0);
77    BOOST_TEST(mrl.m_info.size() == 0u);
78 }
79 
80 //memory_resource* upstream_resource()
test_memory_resource()81 void test_memory_resource()
82 {
83    derived_from_memory_resource d;
84    block_slist bc(d);
85    //Resource stored
86    BOOST_TEST(&bc.upstream_resource() == &d);
87 }
88 
89 //~block_slist()   {  this->release();  }
test_destructor()90 void test_destructor()
91 {
92    memory_resource_logger mrl;
93    {
94       block_slist bc(mrl);
95 
96       //Allocate and trace data
97       char *bufs[AllocCount];
98       for(unsigned i = 0; i != unsigned(AllocCount); ++i){
99          bufs[i] = (char*)bc.allocate(i+1);
100       }
101       (void)bufs;
102       //Should have allocated a new entry
103       BOOST_TEST(mrl.m_info.size() == AllocCount);
104 
105       //Destructor should release all memory
106    }
107    BOOST_TEST(mrl.m_mismatches == 0);
108    BOOST_TEST(mrl.m_info.size() == 0u);
109 }
110 
111 }  //namespace test_block_chain {
112 
test_resource_constructor()113 void test_resource_constructor()
114 {
115    //First constructor, null resource
116    {
117       memory_resource_logger mrl;
118       BOOST_TEST(mrl.m_info.size() == 0u);
119       set_default_resource(&mrl);
120       monotonic_buffer_resource m;
121       //test postconditions
122       BOOST_TEST(m.upstream_resource() == get_default_resource());
123       //test it does not allocate any memory
124       BOOST_TEST(mrl.m_info.size() == 0u);
125       set_default_resource(0);
126    }
127    //First constructor, non-null resource
128    {
129       derived_from_memory_resource dmr;
130       dmr.reset();
131       monotonic_buffer_resource m(&dmr);
132       //test postconditions
133       BOOST_TEST(m.upstream_resource() == &dmr);
134       BOOST_TEST(m.next_buffer_size() == monotonic_buffer_resource::initial_next_buffer_size);
135       BOOST_TEST(m.current_buffer() == 0);
136       //test it does not allocate any memory
137       BOOST_TEST(dmr.do_allocate_called == false);
138    }
139 }
140 
test_initial_size_constructor()141 void test_initial_size_constructor()
142 {
143    //Second constructor, null resource
144    const std::size_t initial_size = monotonic_buffer_resource::initial_next_buffer_size*2;
145    {
146       memory_resource_logger mrl;
147       BOOST_TEST(mrl.m_info.size() == 0u);
148       set_default_resource(&mrl);
149       monotonic_buffer_resource m(initial_size);
150       //test postconditions
151       BOOST_TEST(m.upstream_resource() == get_default_resource());
152       BOOST_TEST(m.next_buffer_size() >= initial_size);
153       BOOST_TEST(m.current_buffer() == 0);
154       //test it does not allocate any memory
155       BOOST_TEST(mrl.m_info.size() == 0u);
156       set_default_resource(0);
157    }
158    //Second constructor, non-null resource
159    {
160       derived_from_memory_resource dmr;
161       dmr.reset();
162       monotonic_buffer_resource m(initial_size, &dmr);
163       //test postconditions
164       BOOST_TEST(m.upstream_resource() == &dmr);
165       BOOST_TEST(m.next_buffer_size() >= initial_size);
166       BOOST_TEST(m.current_buffer() == 0);
167       //test it does not allocate any memory
168       BOOST_TEST(dmr.do_allocate_called == false);
169    }
170 }
171 
test_buffer_constructor()172 void test_buffer_constructor()
173 {
174    const std::size_t BufSz = monotonic_buffer_resource::initial_next_buffer_size*2;
175    unsigned char buf[BufSz];
176    //Third constructor, null resource
177    {
178       memory_resource_logger mrl;
179       BOOST_TEST(mrl.m_info.size() == 0u);
180       set_default_resource(&mrl);
181       monotonic_buffer_resource m(buf, BufSz);
182       //test postconditions
183       BOOST_TEST(m.upstream_resource() == get_default_resource());
184       BOOST_TEST(m.next_buffer_size() >= BufSz*2);
185       BOOST_TEST(m.current_buffer() == buf);
186       //test it does not allocate any memory
187       BOOST_TEST(mrl.m_info.size() == 0u);
188       set_default_resource(0);
189    }
190    //Third constructor, non-null resource
191    {
192       derived_from_memory_resource dmr;
193       dmr.reset();
194       monotonic_buffer_resource m(buf, sizeof(buf), &dmr);
195       //test postconditions
196       BOOST_TEST(m.upstream_resource() == &dmr);
197       BOOST_TEST(m.next_buffer_size() >= sizeof(buf)*2);
198       BOOST_TEST(m.current_buffer() == buf);
199       //test it does not allocate any memory
200       BOOST_TEST(dmr.do_allocate_called == false);
201    }
202    //Check for empty buffers
203    {
204       monotonic_buffer_resource m(buf, 0);
205       BOOST_TEST(m.upstream_resource() == get_default_resource());
206       BOOST_TEST(m.next_buffer_size() > 1);
207       BOOST_TEST(m.current_buffer() == buf);
208    }
209 }
210 
211 struct derived_from_monotonic_buffer_resource
212    : public monotonic_buffer_resource
213 {
derived_from_monotonic_buffer_resourcederived_from_monotonic_buffer_resource214    explicit derived_from_monotonic_buffer_resource(memory_resource *p)
215       : monotonic_buffer_resource(p)
216    {}
217 
derived_from_monotonic_buffer_resourcederived_from_monotonic_buffer_resource218    explicit derived_from_monotonic_buffer_resource(std::size_t initial_size, memory_resource* upstream)
219       : monotonic_buffer_resource(initial_size, upstream)
220    {}
221 
derived_from_monotonic_buffer_resourcederived_from_monotonic_buffer_resource222    explicit derived_from_monotonic_buffer_resource(void* buffer, std::size_t buffer_size, memory_resource* upstream)
223       : monotonic_buffer_resource(buffer, buffer_size, upstream)
224    {}
225 
226    using monotonic_buffer_resource::do_allocate;
227    using monotonic_buffer_resource::do_deallocate;
228    using monotonic_buffer_resource::do_is_equal;
229 };
230 
test_upstream_resource()231 void test_upstream_resource()
232 {
233    //Allocate buffer first to avoid stack-use-after-scope in monotonic_buffer_resource's destructor
234    const std::size_t BufSz = monotonic_buffer_resource::initial_next_buffer_size;
235    boost::move_detail::aligned_storage<BufSz+block_slist::header_size>::type buf;
236    //Test stores the resource and uses it to allocate memory
237    derived_from_memory_resource dmr;
238    dmr.reset();
239    derived_from_monotonic_buffer_resource dmbr(&dmr);
240    //Resource must be stored and initial values given (no current buffer)
241    BOOST_TEST(dmbr.upstream_resource() == &dmr);
242    BOOST_TEST(dmbr.next_buffer_size() == monotonic_buffer_resource::initial_next_buffer_size);
243    BOOST_TEST(dmbr.current_buffer() == 0);
244    //Test it does not allocate any memory
245    BOOST_TEST(dmr.do_allocate_called == false);
246    //Now stub buffer storage it as the return buffer
247    //for "derived_from_memory_resource":
248    dmr.do_allocate_return = &buf;
249    //Test that allocation uses the upstream_resource()
250    void *addr = dmbr.do_allocate(1u, 1u);
251    //Test returns stubbed memory with the internal initial size plus metadata size
252    BOOST_TEST(addr > (char*)&buf);
253    BOOST_TEST(addr < (char*)(&buf+1));
254    BOOST_TEST(dmr.do_allocate_called == true);
255    BOOST_TEST(dmr.do_allocate_bytes > BufSz);
256    //Alignment for the resource must be max_align
257    BOOST_TEST(dmr.do_allocate_alignment == memory_resource::max_align);
258 }
259 
test_do_allocate()260 void test_do_allocate()
261 {
262    memory_resource_logger mrl;
263    {
264       std::size_t remaining_storage = 0u;
265       derived_from_monotonic_buffer_resource dmbr(&mrl);
266       //First test, no buffer
267       {
268          dmbr.do_allocate(1, 1);
269          //It should allocate initial size
270          BOOST_TEST(mrl.m_info.size() == 1u);
271          //... which requests the initial size plus the header size to the allcoator
272          BOOST_TEST(mrl.m_info[0].bytes == monotonic_buffer_resource::initial_next_buffer_size+block_slist::header_size);
273          std::size_t remaining = dmbr.remaining_storage(1u);
274          //Remaining storage should be one less than initial, as we requested 1 byte with minimal alignment
275          BOOST_TEST(remaining == monotonic_buffer_resource::initial_next_buffer_size-1u);
276          remaining_storage = remaining;
277       }
278       //Now ask for more internal storage with misaligned current buffer
279       {
280          //Test wasted space
281          std::size_t wasted_due_to_alignment;
282          dmbr.remaining_storage(4u, wasted_due_to_alignment);
283          BOOST_TEST(wasted_due_to_alignment == 3u);
284          dmbr.do_allocate(4, 4);
285          //It should not have allocated
286          BOOST_TEST(mrl.m_info.size() == 1u);
287          std::size_t remaining = dmbr.remaining_storage(1u);
288          //We wasted some bytes due to alignment plus 4 bytes of real storage
289          BOOST_TEST(remaining == remaining_storage - 4 - wasted_due_to_alignment);
290          remaining_storage = remaining;
291       }
292       //Now request the same alignment to test no storage is wasted
293       {
294          std::size_t wasted_due_to_alignment;
295          std::size_t remaining = dmbr.remaining_storage(1u, wasted_due_to_alignment);
296          BOOST_TEST(mrl.m_info.size() == 1u);
297          dmbr.do_allocate(4, 4);
298          //It should not have allocated
299          BOOST_TEST(mrl.m_info.size() == 1u);
300          remaining = dmbr.remaining_storage(1u);
301          //We wasted no bytes due to alignment plus 4 bytes of real storage
302          BOOST_TEST(remaining == remaining_storage - 4u);
303          remaining_storage = remaining;
304       }
305       //Now exhaust the remaining storage with 2 byte alignment (the last allocation
306       //was 4 bytes with 4 byte alignment) so it should be already 2-byte aligned.
307       {
308          dmbr.do_allocate(remaining_storage, 2);
309          std::size_t wasted_due_to_alignment;
310          std::size_t remaining = dmbr.remaining_storage(1u, wasted_due_to_alignment);
311          BOOST_TEST(wasted_due_to_alignment == 0u);
312          BOOST_TEST(remaining == 0u);
313          //It should not have allocated
314          BOOST_TEST(mrl.m_info.size() == 1u);
315          remaining_storage = 0u;
316       }
317       //The next allocation should trigger the upstream resource, even with a 1 byte
318       //allocation.
319       {
320          dmbr.do_allocate(1u, 1u);
321          BOOST_TEST(mrl.m_info.size() == 2u);
322          //The next allocation should be geometrically bigger.
323          BOOST_TEST(mrl.m_info[1].bytes == 2*monotonic_buffer_resource::initial_next_buffer_size+block_slist::header_size);
324          std::size_t wasted_due_to_alignment;
325          //For a 2 byte alignment one byte will be wasted from the previous 1 byte allocation
326          std::size_t remaining = dmbr.remaining_storage(2u, wasted_due_to_alignment);
327          BOOST_TEST(wasted_due_to_alignment == 1u);
328          BOOST_TEST(remaining == (mrl.m_info[1].bytes - 1u  - wasted_due_to_alignment - block_slist::header_size));
329          //It should not have allocated
330          remaining_storage = dmbr.remaining_storage(1u);
331       }
332       //Now try a bigger than next allocation and see if next_buffer_size is doubled.
333       {
334          std::size_t next_alloc = 5*monotonic_buffer_resource::initial_next_buffer_size;
335          dmbr.do_allocate(next_alloc, 1u);
336          BOOST_TEST(mrl.m_info.size() == 3u);
337          //The next allocation should be geometrically bigger.
338          BOOST_TEST(mrl.m_info[2].bytes == 8*monotonic_buffer_resource::initial_next_buffer_size+block_slist::header_size);
339          remaining_storage = dmbr.remaining_storage(1u);
340       }
341    }
342    //derived_from_monotonic_buffer_resource dmbr(&mrl) is destroyed
343    BOOST_TEST(mrl.m_mismatches == 0u);
344    BOOST_TEST(mrl.m_info.size() == 0u);
345 
346    //Now use a local buffer
347    {
348       boost::move_detail::aligned_storage
349          <monotonic_buffer_resource::initial_next_buffer_size>::type buf;
350       //Supply an external buffer
351       derived_from_monotonic_buffer_resource dmbr(&buf, sizeof(buf), &mrl);
352       BOOST_TEST(dmbr.remaining_storage(1u) == sizeof(buf));
353       //Allocate all remaining storage
354       dmbr.do_allocate(dmbr.remaining_storage(1u), 1u);
355       //No new allocation should have occurred
356       BOOST_TEST(mrl.m_info.size() == 0u);
357       BOOST_TEST(dmbr.remaining_storage(1u) == 0u);
358    }
359    BOOST_TEST(mrl.m_mismatches == 0u);
360    BOOST_TEST(mrl.m_info.size() == 0u);
361 }
362 
test_do_deallocate()363 void test_do_deallocate()
364 {
365    memory_resource_logger mrl;
366    const std::size_t initial_size = 1u;
367    {
368       derived_from_monotonic_buffer_resource dmbr(initial_size, &mrl);
369       //First test, no buffer
370       const unsigned iterations = 8;
371       char *bufs[iterations];
372       std::size_t sizes[iterations];
373       //Test each iteration allocates memory
374       for(unsigned i = 0; i != iterations; ++i)
375       {
376          sizes[i] = dmbr.remaining_storage()+1;
377          bufs[i]  = (char*)dmbr.do_allocate(sizes[i], 1);
378          BOOST_TEST(mrl.m_info.size() == (i+1));
379       }
380       std::size_t remaining = dmbr.remaining_storage();
381       //Test do_deallocate does not release any storage
382       for(unsigned i = 0; i != iterations; ++i)
383       {
384          dmbr.do_deallocate(bufs[i], sizes[i], 1u);
385          BOOST_TEST(mrl.m_info.size() == iterations);
386          BOOST_TEST(remaining == dmbr.remaining_storage());
387          BOOST_TEST(mrl.m_mismatches == 0u);
388       }
389    }
390 }
391 
test_do_is_equal()392 void test_do_is_equal()
393 {
394    //! <b>Returns</b>:
395    //!   `this == dynamic_cast<const monotonic_buffer_resource*>(&other)`.
396    memory_resource_logger mrl;
397    derived_from_monotonic_buffer_resource dmbr(&mrl);
398    derived_from_monotonic_buffer_resource dmbr2(&mrl);
399    BOOST_TEST(true == dmbr.do_is_equal(dmbr));
400    BOOST_TEST(false == dmbr.do_is_equal(dmbr2));
401    //A different type should be always different
402    derived_from_memory_resource dmr;
403    BOOST_TEST(false == dmbr.do_is_equal(dmr));
404 }
405 
test_release()406 void test_release()
407 {
408    {
409       memory_resource_logger mrl;
410       const std::size_t initial_size = 1u;
411       derived_from_monotonic_buffer_resource dmbr(initial_size, &mrl);
412       //First test, no buffer
413       const unsigned iterations = 8;
414       //Test each iteration allocates memory
415       for(unsigned i = 0; i != iterations; ++i)
416       {
417          dmbr.do_allocate(dmbr.remaining_storage()+1, 1);
418          BOOST_TEST(mrl.m_info.size() == (i+1));
419       }
420       //Release and check memory was released
421       dmbr.release();
422       BOOST_TEST(mrl.m_mismatches == 0u);
423       BOOST_TEST(mrl.m_info.size() == 0u);
424    }
425    //Now use a local buffer
426    {
427       boost::move_detail::aligned_storage
428          <monotonic_buffer_resource::initial_next_buffer_size>::type buf;
429       //Supply an external buffer
430       monotonic_buffer_resource monr(&buf, sizeof(buf));
431       memory_resource &mr = monr;
432       BOOST_TEST(monr.remaining_storage(1u) == sizeof(buf));
433       //Allocate all remaining storage
434       mr.allocate(monr.remaining_storage(1u), 1u);
435       BOOST_TEST(monr.current_buffer() == ((char*)&buf + sizeof(buf)));
436       //No new allocation should have occurred
437       BOOST_TEST(monr.remaining_storage(1u) == 0u);
438       //Release and check memory was released and the original buffer is back
439       monr.release();
440       BOOST_TEST(monr.remaining_storage(1u) == sizeof(buf));
441       BOOST_TEST(monr.current_buffer() == &buf);
442    }
443 }
444 
test_destructor()445 void test_destructor()
446 {
447    memory_resource_logger mrl;
448    const std::size_t initial_size = 1u;
449    {
450       derived_from_monotonic_buffer_resource dmbr(initial_size, &mrl);
451       //First test, no buffer
452       const unsigned iterations = 8;
453       //Test each iteration allocates memory
454       for(unsigned i = 0; i != iterations; ++i)
455       {
456          dmbr.do_allocate(dmbr.remaining_storage()+1, 1);
457          BOOST_TEST(mrl.m_info.size() == (i+1));
458       }
459    }  //dmbr is destroyed, memory should be released
460    BOOST_TEST(mrl.m_mismatches == 0u);
461    BOOST_TEST(mrl.m_info.size() == 0u);
462 }
463 
main()464 int main()
465 {
466    test_block_chain::test_constructor();
467    test_block_chain::test_allocate();
468    test_block_chain::test_release();
469    test_block_chain::test_memory_resource();
470    test_block_chain::test_destructor();
471 
472    test_resource_constructor();
473    test_initial_size_constructor();
474    test_buffer_constructor();
475 
476    test_upstream_resource();
477    test_do_allocate();
478    test_do_deallocate();
479    test_do_is_equal();
480    test_release();
481    test_destructor();
482    return ::boost::report_errors();
483 }
484