From 260b506520b8a29470abfdf589b62c2cf0c2acc0 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 10 Jun 2017 07:48:17 -0700 Subject: [PATCH] multi_buffer members and coverage fix #458 --- CHANGELOG.md | 1 + include/beast/core/impl/multi_buffer.ipp | 329 ++++++++++------ include/beast/core/multi_buffer.hpp | 214 ++++++----- test/core/multi_buffer.cpp | 457 ++++++++++++++++++++--- 4 files changed, 721 insertions(+), 280 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4000b307..84dc9247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Version 54: * static_buffer coverage * flat_buffer coverage +* multi_buffer coverage -------------------------------------------------------------------------------- diff --git a/include/beast/core/impl/multi_buffer.ipp b/include/beast/core/impl/multi_buffer.ipp index 14799a9f..aed523a4 100644 --- a/include/beast/core/impl/multi_buffer.ipp +++ b/include/beast/core/impl/multi_buffer.ipp @@ -425,6 +425,34 @@ basic_multi_buffer() { } +template +basic_multi_buffer:: +basic_multi_buffer(std::size_t limit) + : max_(limit) + , out_(list_.end()) +{ +} + +template +basic_multi_buffer:: +basic_multi_buffer(Allocator const& alloc) + : detail::empty_base_optimization< + allocator_type>(alloc) + , out_(list_.end()) +{ +} + +template +basic_multi_buffer:: +basic_multi_buffer(std::size_t limit, + Allocator const& alloc) + : detail::empty_base_optimization< + allocator_type>(alloc) + , max_(limit) + , out_(list_.end()) +{ +} + template basic_multi_buffer:: basic_multi_buffer(basic_multi_buffer&& other) @@ -450,34 +478,54 @@ basic_multi_buffer(basic_multi_buffer&& other) template basic_multi_buffer:: basic_multi_buffer(basic_multi_buffer&& other, - allocator_type const& alloc) - : basic_multi_buffer(other.max_, alloc) + Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , max_(other.max_) { - using boost::asio::buffer_copy; if(this->member() != other.member()) - commit(buffer_copy(prepare(other.size()), other.data())); + { + out_ = list_.end(); + copy_from(other); + other.reset(); + } else - move_assign(other, std::true_type{}); + { + auto const at_end = + other.out_ == other.list_.end(); + list_ = std::move(other.list_); + out_ = at_end ? list_.end() : other.out_; + in_size_ = other.in_size_; + in_pos_ = other.in_pos_; + out_pos_ = other.out_pos_; + out_end_ = other.out_end_; + other.in_size_ = 0; + other.out_ = other.list_.end(); + other.in_pos_ = 0; + other.out_pos_ = 0; + other.out_end_ = 0; + } } template basic_multi_buffer:: basic_multi_buffer(basic_multi_buffer const& other) - : basic_multi_buffer(other.max_, + : detail::empty_base_optimization( alloc_traits::select_on_container_copy_construction(other.member())) + , max_(other.max_) + , out_(list_.end()) { - commit(boost::asio::buffer_copy( - prepare(other.size()), other.data())); + copy_from(other); } template basic_multi_buffer:: basic_multi_buffer(basic_multi_buffer const& other, - allocator_type const& alloc) - : basic_multi_buffer(other.max_, alloc) + Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , max_(other.max_) + , out_(list_.end()) { - commit(boost::asio::buffer_copy( - prepare(other.size()), other.data())); + copy_from(other); } template @@ -485,10 +533,9 @@ template basic_multi_buffer:: basic_multi_buffer( basic_multi_buffer const& other) - : basic_multi_buffer(other.max_) + : out_(list_.end()) { - using boost::asio::buffer_copy; - commit(buffer_copy(prepare(other.size()), other.data())); + copy_from(other); } template @@ -497,35 +544,11 @@ basic_multi_buffer:: basic_multi_buffer( basic_multi_buffer const& other, allocator_type const& alloc) - : basic_multi_buffer(other.max_, alloc) -{ - using boost::asio::buffer_copy; - commit(buffer_copy(prepare(other.size()), other.data())); -} - -template -basic_multi_buffer:: -basic_multi_buffer(std::size_t limit) - : max_(limit) + : detail::empty_base_optimization(alloc) + , max_(other.max_) , out_(list_.end()) { - if(max_ <= 0) - BOOST_THROW_EXCEPTION(std::invalid_argument{ - "invalid limit"}); -} - -template -basic_multi_buffer:: -basic_multi_buffer(std::size_t limit, - Allocator const& alloc) - : detail::empty_base_optimization< - allocator_type>(alloc) - , max_(limit) - , out_(list_.end()) -{ - if(max_ <= 0) - BOOST_THROW_EXCEPTION(std::invalid_argument{ - "invalid limit"}); + copy_from(other); } template @@ -536,11 +559,10 @@ operator=(basic_multi_buffer&& other) -> { if(this == &other) return *this; - // VFALCO If any memory allocated we could use it first? - clear(); + reset(); max_ = other.max_; - move_assign(other, std::integral_constant{}); + move_assign(other, typename + alloc_traits::propagate_on_container_move_assignment{}); return *this; } @@ -552,12 +574,8 @@ basic_multi_buffer& { if(this == &other) return *this; - using boost::asio::buffer_copy; - clear(); - max_ = other.max_; - copy_assign(other, std::integral_constant{}); - commit(buffer_copy(prepare(other.size()), other.data())); + copy_assign(other, typename + alloc_traits::propagate_on_container_copy_assignment{}); return *this; } @@ -569,10 +587,9 @@ operator=( basic_multi_buffer const& other) -> basic_multi_buffer& { - using boost::asio::buffer_copy; - clear(); + reset(); max_ = other.max_; - commit(buffer_copy(prepare(other.size()), other.data())); + copy_from(other); return *this; } @@ -609,8 +626,11 @@ prepare(size_type n) -> BOOST_THROW_EXCEPTION(std::length_error{ "dynamic buffer overflow"}); list_type reuse; + std::size_t total = in_size_; + // put all empty buffers on reuse list if(out_ != list_.end()) { + total += out_->size() - out_pos_; if(out_ != list_.iterator_to(list_.back())) { out_end_ = out_->size(); @@ -635,11 +655,13 @@ prepare(size_type n) -> debug_check(); #endif } + // get space from reuse buffers while(n > 0 && ! reuse.empty()) { auto& e = reuse.front(); reuse.erase(reuse.iterator_to(e)); list_.push_back(e); + total += e.size(); if(n > e.size()) { out_end_ = e.size(); @@ -654,16 +676,23 @@ prepare(size_type n) -> debug_check(); #endif } - while(n > 0) + BOOST_ASSERT(total <= max_); + for(auto it = reuse.begin(); it != reuse.end();) + { + auto& e = *it++; + reuse.erase(list_.iterator_to(e)); + delete_element(e); + } + if(n > 0) { static auto const growth_factor = 2.0f; auto const size = std::min( - max_ - in_size_, + max_ - total, std::max({ static_cast( in_size_ * growth_factor - in_size_), - 1024, + 512, n})); auto& e = *reinterpret_cast(static_cast< void*>(alloc_traits::allocate(this->member(), @@ -672,29 +701,11 @@ prepare(size_type n) -> list_.push_back(e); if(out_ == list_.end()) out_ = list_.iterator_to(e); - if(n >= e.size()) - { - out_end_ = e.size(); - n -= e.size(); - } - else - { - out_end_ = n; - n = 0; - } + out_end_ = n; #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } - for(auto it = reuse.begin(); it != reuse.end();) - { - auto& e = *it++; - reuse.erase(list_.iterator_to(e)); - auto const len = e.size() + sizeof(e); - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate(this->member(), - reinterpret_cast(&e), len); - } return mutable_buffers_type(*this); } @@ -752,12 +763,12 @@ consume(size_type n) { if(list_.empty()) return; - for(;;) { if(list_.begin() != out_) { - auto const avail = list_.front().size() - in_pos_; + auto const avail = + list_.front().size() - in_pos_; if(n < avail) { in_size_ -= n; @@ -772,10 +783,7 @@ consume(size_type n) in_pos_ = 0; auto& e = list_.front(); list_.erase(list_.iterator_to(e)); - auto const len = e.size() + sizeof(e); - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate(this->member(), - reinterpret_cast(&e), len); + delete_element(e); #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif @@ -814,13 +822,36 @@ consume(size_type n) } template +inline void basic_multi_buffer:: -clear() +delete_element(element& e) +{ + auto const len = sizeof(e) + e.size(); + alloc_traits::destroy(this->member(), &e); + alloc_traits::deallocate(this->member(), + reinterpret_cast(&e), len); +} + +template +inline +void +basic_multi_buffer:: +delete_list() +{ + for(auto iter = list_.begin(); iter != list_.end();) + delete_element(*iter++); +} + +template +inline +void +basic_multi_buffer:: +reset() { delete_list(); list_.clear(); - out_ = list_.begin(); + out_ = list_.end(); in_size_ = 0; in_pos_ = 0; out_pos_ = 0; @@ -828,21 +859,38 @@ clear() } template +template +inline +void +basic_multi_buffer:: +copy_from(DynamicBuffer const& buffer) +{ + if(buffer.size() == 0) + return; + using boost::asio::buffer_copy; + commit(buffer_copy( + prepare(buffer.size()), buffer.data())); +} + +template +inline void basic_multi_buffer:: move_assign(basic_multi_buffer& other, std::false_type) { - using boost::asio::buffer_copy; if(this->member() != other.member()) { - commit(buffer_copy(prepare(other.size()), other.data())); - other.clear(); + copy_from(other); + other.reset(); } else + { move_assign(other, std::true_type{}); + } } template +inline void basic_multi_buffer:: move_assign(basic_multi_buffer& other, std::true_type) @@ -866,36 +914,95 @@ move_assign(basic_multi_buffer& other, std::true_type) } template +inline void basic_multi_buffer:: copy_assign( basic_multi_buffer const& other, std::false_type) { - beast::detail::ignore_unused(other); + reset(); + max_ = other.max_; + copy_from(other); } template +inline void basic_multi_buffer:: copy_assign( basic_multi_buffer const& other, std::true_type) { + reset(); + max_ = other.max_; this->member() = other.member(); + copy_from(other); +} + +template +inline +void +basic_multi_buffer:: +swap(basic_multi_buffer& other) +{ + swap(other, typename + alloc_traits::propagate_on_container_swap{}); +} + +template +inline +void +basic_multi_buffer:: +swap(basic_multi_buffer& other, std::true_type) +{ + using std::swap; + auto const at_end0 = + out_ == list_.end(); + auto const at_end1 = + other.out_ == other.list_.end(); + swap(this->member(), other.member()); + swap(list_, other.list_); + swap(out_, other.out_); + if(at_end1) + out_ = list_.end(); + if(at_end0) + other.out_ = other.list_.end(); + swap(in_size_, other.in_size_); + swap(in_pos_, other.in_pos_); + swap(out_pos_, other.out_pos_); + swap(out_end_, other.out_end_); +} + +template +inline +void +basic_multi_buffer:: +swap(basic_multi_buffer& other, std::false_type) +{ + BOOST_ASSERT(this->member() == other.member()); + using std::swap; + auto const at_end0 = + out_ == list_.end(); + auto const at_end1 = + other.out_ == other.list_.end(); + swap(list_, other.list_); + swap(out_, other.out_); + if(at_end1) + out_ = list_.end(); + if(at_end0) + other.out_ = other.list_.end(); + swap(in_size_, other.in_size_); + swap(in_pos_, other.in_pos_); + swap(out_pos_, other.out_pos_); + swap(out_end_, other.out_end_); } template void -basic_multi_buffer:: -delete_list() +swap( + basic_multi_buffer& lhs, + basic_multi_buffer& rhs) { - for(auto iter = list_.begin(); iter != list_.end();) - { - auto& e = *iter++; - auto const n = e.size() + sizeof(e); - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate(this->member(), - reinterpret_cast(&e), n); - } + lhs.swap(rhs); } template @@ -939,30 +1046,6 @@ debug_check() const #endif } -#if 0 -template -std::size_t -read_size_helper( - basic_multi_buffer const& buffer, - std::size_t max_size) -{ - BOOST_ASSERT(max_size >= 1); - auto const size = buffer.size(); - auto const limit = buffer.max_size() - size; - if(limit <= 0) - BOOST_THROW_EXCEPTION(std::length_error{ - "dynamic buffer overflow"}); - - auto const avail = std::min( - buffer.capacity() - size, max_size); - if(avail > 0) - return avail; // avoid allocation - return std::min( - std::min(max_size, buffer.max_size() - size), - avail + buffer.alloc_size()); -} -#endif - } // beast #endif diff --git a/include/beast/core/multi_buffer.hpp b/include/beast/core/multi_buffer.hpp index 7c404767..4b961756 100644 --- a/include/beast/core/multi_buffer.hpp +++ b/include/beast/core/multi_buffer.hpp @@ -96,120 +96,121 @@ public: #endif - /// Destructor. + /// Destructor ~basic_multi_buffer(); - /// Default constructor. + /** Constructor + + Upon construction, capacity will be zero. + */ basic_multi_buffer(); - /** Move constructor. - - The new object will have the input sequence of - the other stream buffer, and an empty output sequence. - - @note After the move, the moved-from object will have - an empty input and output sequence, with no internal - buffers allocated. - */ - basic_multi_buffer(basic_multi_buffer&&); - - /** Move constructor. - - The new object will have the input sequence of - the other stream buffer, and an empty output sequence. - - @note After the move, the moved-from object will have - an empty input and output sequence, with no internal - buffers allocated. - - @param alloc The allocator to associate with the - stream buffer. - */ - basic_multi_buffer(basic_multi_buffer&&, - allocator_type const& alloc); - - /** Copy constructor. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - */ - basic_multi_buffer(basic_multi_buffer const&); - - /** Copy constructor. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - - @param alloc The allocator to associate with the - stream buffer. - */ - basic_multi_buffer(basic_multi_buffer const&, - allocator_type const& alloc); - - /** Copy constructor. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - */ - template - basic_multi_buffer(basic_multi_buffer const&); - - /** Copy constructor. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - - @param alloc The allocator to associate with the - stream buffer. - */ - template - basic_multi_buffer(basic_multi_buffer const&, - allocator_type const& alloc); - /** Constructor. - @param limit The maximum allowed sum of the input and - output sequence sizes. + @param limit The setting for @ref max_size. */ explicit basic_multi_buffer(std::size_t limit); /** Constructor. - @param limit The maximum allowed sum of the input and - output sequence sizes. + @param alloc The allocator to use. + */ + basic_multi_buffer(Allocator const& alloc); + + /** Constructor. + + @param limit The setting for @ref max_size. @param alloc The allocator to use. */ basic_multi_buffer( std::size_t limit, Allocator const& alloc); - /** Move assignment. + /** Move constructor - This object will have the input sequence of - the other stream buffer, and an empty output sequence. + After the move, `*this` will have an empty output sequence. - @note After the move, the moved-from object will have - an empty input and output sequence, with no internal - buffers allocated. + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. */ - basic_multi_buffer& - operator=(basic_multi_buffer&&); + basic_multi_buffer(basic_multi_buffer&& other); - /** Copy assignment. + /** Move constructor - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + + @param alloc The allocator to use. */ - basic_multi_buffer& operator=(basic_multi_buffer const&); + basic_multi_buffer(basic_multi_buffer&& other, + Allocator const& alloc); - /** Copy assignment. + /** Copy constructor. - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. + @param other The object to copy from. + */ + basic_multi_buffer(basic_multi_buffer const& other); + + /** Copy constructor + + @param other The object to copy from. + + @param alloc The allocator to use. + */ + basic_multi_buffer(basic_multi_buffer const& other, + Allocator const& alloc); + + /** Copy constructor. + + @param other The object to copy from. */ template - basic_multi_buffer& operator=(basic_multi_buffer const&); + basic_multi_buffer(basic_multi_buffer< + OtherAlloc> const& other); + + /** Copy constructor. + + @param other The object to copy from. + + @param alloc The allocator to use. + */ + template + basic_multi_buffer(basic_multi_buffer< + OtherAlloc> const& other, allocator_type const& alloc); + + /** Move assignment + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + */ + basic_multi_buffer& + operator=(basic_multi_buffer&& other); + + /** Copy assignment + + After the copy, `*this` will have an empty output sequence. + + @param other The object to copy from. + */ + basic_multi_buffer& operator=(basic_multi_buffer const& other); + + /** Copy assignment + + After the copy, `*this` will have an empty output sequence. + + @param other The object to copy from. + */ + template + basic_multi_buffer& operator=( + basic_multi_buffer const& other); /// Returns a copy of the associated allocator. allocator_type @@ -263,9 +264,29 @@ public: void consume(size_type n); -private: + template + friend void - clear(); + swap( + basic_multi_buffer& lhs, + basic_multi_buffer& rhs); + +private: + template + friend class basic_multi_buffer; + + void + delete_element(element& e); + + void + delete_list(); + + void + reset(); + + template + void + copy_from(DynamicBuffer const& other); void move_assign(basic_multi_buffer& other, std::false_type); @@ -280,21 +301,18 @@ private: copy_assign(basic_multi_buffer const& other, std::true_type); void - delete_list(); + swap(basic_multi_buffer&); + + void + swap(basic_multi_buffer&, std::true_type); + + void + swap(basic_multi_buffer&, std::false_type); void debug_check() const; }; -#if 0 -/// Helper for boost::asio::read_until -template -std::size_t -read_size_helper( - basic_multi_buffer const& buffer, - std::size_t max_size); -#endif - /// A typical multi buffer using multi_buffer = basic_multi_buffer>; diff --git a/test/core/multi_buffer.cpp b/test/core/multi_buffer.cpp index ea68a94c..e97e7dbb 100644 --- a/test/core/multi_buffer.cpp +++ b/test/core/multi_buffer.cpp @@ -9,7 +9,9 @@ #include #include "buffer_test.hpp" + #include +#include #include #include #include @@ -26,22 +28,14 @@ BOOST_STATIC_ASSERT(is_dynamic_buffer::value); class multi_buffer_test : public beast::unit_test::suite { public: - template - static - std::string - to_string(ConstBufferSequence const& bs) - { - return boost::lexical_cast< - std::string>(buffers(bs)); - } - template static bool - eq(basic_multi_buffer const& sb1, - basic_multi_buffer const& sb2) + eq(basic_multi_buffer const& mb1, + basic_multi_buffer const& mb2) { - return to_string(sb1.data()) == to_string(sb2.data()); + return test::to_string(mb1.data()) == + test::to_string(mb2.data()); } template @@ -62,8 +56,10 @@ public: u = std::forward(v); } - void testSpecialMembers() + void + testMatrix1() { + using namespace test; using boost::asio::buffer; std::string const s = "Hello, world"; BEAST_EXPECT(s.size() == 12); @@ -72,27 +68,27 @@ public: for(std::size_t y = 1; y < 4; ++y) { std::size_t z = s.size() - (x + y); { - multi_buffer b;//(i); + multi_buffer b; b.commit(buffer_copy(b.prepare(x), buffer(s.data(), x))); b.commit(buffer_copy(b.prepare(y), buffer(s.data()+x, y))); b.commit(buffer_copy(b.prepare(z), buffer(s.data()+x+y, z))); BEAST_EXPECT(to_string(b.data()) == s); { - multi_buffer sb2(b); - BEAST_EXPECT(eq(b, sb2)); + multi_buffer mb2{b}; + BEAST_EXPECT(eq(b, mb2)); } { - multi_buffer sb2; - sb2 = b; - BEAST_EXPECT(eq(b, sb2)); + multi_buffer mb2; + mb2 = b; + BEAST_EXPECT(eq(b, mb2)); } { - multi_buffer sb2(std::move(b)); - BEAST_EXPECT(to_string(sb2.data()) == s); + multi_buffer mb2{std::move(b)}; + BEAST_EXPECT(to_string(mb2.data()) == s); expect_size(0, b.data()); - b = std::move(sb2); + b = std::move(mb2); BEAST_EXPECT(to_string(b.data()) == s); - expect_size(0, sb2.data()); + expect_size(0, mb2.data()); } self_assign(b, b); BEAST_EXPECT(to_string(b.data()) == s); @@ -100,40 +96,12 @@ public: BEAST_EXPECT(to_string(b.data()) == s); } }}} - try - { - multi_buffer sb0(0); - fail(); - } - catch(std::exception const&) - { - pass(); - } } void - testAllocator() - { - using test::test_allocator; - // VFALCO This needs work - { - using alloc_type = - test_allocator; - using type = basic_multi_buffer; - type b; - } - { - using alloc_type = - test_allocator; - using type = basic_multi_buffer; - type b; - type b2(b); - type b3(b, alloc_type{}); - } - } - - void testMatrix() + testMatrix2() { + using namespace test; using boost::asio::buffer; using boost::asio::buffer_size; std::string const s = "Hello, world"; @@ -146,7 +114,7 @@ public: std::size_t z = s.size() - (x + y); std::size_t v = s.size() - (t + u); { - multi_buffer b;//(i); + multi_buffer b; { auto d = b.prepare(z); BEAST_EXPECT(buffer_size(d) == z); @@ -226,7 +194,8 @@ public: }}}}} } - void testIterators() + void + testIterators() { using boost::asio::buffer_size; multi_buffer b; @@ -240,14 +209,384 @@ public: b.commit(2); } - void run() override + void + testMembers() + { + using namespace test; + + // compare equal + using equal_t = test::test_allocator; + + // compare not equal + using unequal_t = test::test_allocator; + + // construction + { + { + multi_buffer b; + BEAST_EXPECT(b.capacity() == 0); + } + { + multi_buffer b{500}; + BEAST_EXPECT(b.capacity() == 0); + BEAST_EXPECT(b.max_size() == 500); + } + { + unequal_t a1; + basic_multi_buffer b{a1}; + BEAST_EXPECT(b.get_allocator() == a1); + BEAST_EXPECT(b.get_allocator() != unequal_t{}); + } + } + + // move construction + { + { + basic_multi_buffer b1{30}; + BEAST_EXPECT(b1.get_allocator()->nmove == 0); + ostream(b1) << "Hello"; + basic_multi_buffer b2{std::move(b1)}; + BEAST_EXPECT(b2.get_allocator()->nmove == 1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + // allocators equal + { + basic_multi_buffer b1{30}; + ostream(b1) << "Hello"; + equal_t a; + basic_multi_buffer b2{std::move(b1), a}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + { + // allocators unequal + basic_multi_buffer b1{30}; + ostream(b1) << "Hello"; + unequal_t a; + basic_multi_buffer b2{std::move(b1), a}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + } + + // copy construction + { + { + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2{b1}; + BEAST_EXPECT(b1.get_allocator() == b2.get_allocator()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_multi_buffer b1; + ostream(b1) << "Hello"; + unequal_t a; + basic_multi_buffer b2(b1, a); + BEAST_EXPECT(b1.get_allocator() != b2.get_allocator()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2(b1); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_multi_buffer b1; + ostream(b1) << "Hello"; + equal_t a; + basic_multi_buffer b2(b1, a); + BEAST_EXPECT(b2.get_allocator() == a); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // move assignment + { + { + multi_buffer b1; + ostream(b1) << "Hello"; + multi_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_move_assignment : true + using pocma_t = test::test_allocator; + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_move_assignment : false + using pocma_t = test::test_allocator; + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // copy assignment + { + { + multi_buffer b1; + ostream(b1) << "Hello"; + multi_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + basic_multi_buffer b3; + b3 = b2; + BEAST_EXPECT(to_string(b3.data()) == "Hello"); + } + { + // propagate_on_container_copy_assignment : true + using pocca_t = test::test_allocator; + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_copy_assignment : false + using pocca_t = test::test_allocator; + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // prepare + { + { + multi_buffer b{100}; + try + { + b.prepare(b.max_size() + 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + string_view const s = "Hello, world!"; + multi_buffer b1{64}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.max_size() == 64); + BEAST_EXPECT(b1.capacity() == 0); + ostream(b1) << s; + BEAST_EXPECT(to_string(b1.data()) == s); + { + multi_buffer b2{b1}; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + { + multi_buffer b2{64}; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + } + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.commit(1); + BEAST_EXPECT(b.size() == 1); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.size() == 1); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1500); + BEAST_EXPECT(b.capacity() >= 1000); + } + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.commit(1); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(2000); + BEAST_EXPECT(b.capacity() >= 2000); + b.commit(2); + } + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(2000); + BEAST_EXPECT(b.capacity() >= 2000); + b.prepare(4000); + BEAST_EXPECT(b.capacity() >= 4000); + b.prepare(50); + BEAST_EXPECT(b.capacity() >= 50); + } + } + + // commit + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.commit(1000); + BEAST_EXPECT(b.size() == 1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.consume(1000); + BEAST_EXPECT(b.size() == 0); + BEAST_EXPECT(b.capacity() == 0); + b.prepare(1000); + b.commit(650); + BEAST_EXPECT(b.size() == 650); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1650); + b.commit(100); + BEAST_EXPECT(b.size() == 750); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 2000); + b.commit(500); + } + + // consume + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.commit(1000); + BEAST_EXPECT(b.size() == 1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 2000); + b.commit(750); + BEAST_EXPECT(b.size() == 1750); + b.consume(500); + BEAST_EXPECT(b.size() == 1250); + b.consume(500); + BEAST_EXPECT(b.size() == 750); + b.prepare(250); + b.consume(750); + BEAST_EXPECT(b.size() == 0); + b.prepare(1000); + b.commit(800); + BEAST_EXPECT(b.size() == 800); + b.prepare(1000); + b.commit(600); + BEAST_EXPECT(b.size() == 1400); + b.consume(1400); + BEAST_EXPECT(b.size() == 0); + } + + // swap + { + { + // propagate_on_container_swap : true + using pocs_t = test::test_allocator; + pocs_t a1, a2; + BEAST_EXPECT(a1 != a2); + basic_multi_buffer b1{a1}; + ostream(b1) << "Hello"; + basic_multi_buffer b2{a2}; + BEAST_EXPECT(b1.get_allocator() == a1); + BEAST_EXPECT(b2.get_allocator() == a2); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator() == a2); + BEAST_EXPECT(b2.get_allocator() == a1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator() == a1); + BEAST_EXPECT(b2.get_allocator() == a2); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(b2.size() == 0); + } + { + // propagate_on_container_swap : false + using pocs_t = test::test_allocator; + pocs_t a1, a2; + BEAST_EXPECT(a1 == a2); + BEAST_EXPECT(a1.id() != a2.id()); + basic_multi_buffer b1{a1}; + ostream(b1) << "Hello"; + basic_multi_buffer b2{a2}; + BEAST_EXPECT(b1.get_allocator() == a1); + BEAST_EXPECT(b2.get_allocator() == a2); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator().id() == a1.id()); + BEAST_EXPECT(b2.get_allocator().id() == a2.id()); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator().id() == a1.id()); + BEAST_EXPECT(b2.get_allocator().id() == a2.id()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(b2.size() == 0); + } + } + + // read_size_helper + { + using detail::read_size_helper; + multi_buffer b{10}; + BEAST_EXPECT(read_size_helper(b, 512) == 10); + b.prepare(4); + b.commit(4); + BEAST_EXPECT(read_size_helper(b, 512) == 6); + b.consume(2); + BEAST_EXPECT(read_size_helper(b, 512) == 8); + b.prepare(8); + b.commit(8); + BEAST_EXPECT(read_size_helper(b, 512) == 0); + } + } + + void + run() override { test::check_read_size_helper(); - testSpecialMembers(); - testAllocator(); - testMatrix(); + testMatrix1(); + testMatrix2(); testIterators(); + testMembers(); } };