Fixes and tidying for flat_buffer:

* Fix reserve() size calculation
* Improved test coverage
This commit is contained in:
Vinnie Falco
2018-12-15 15:49:39 -08:00
parent 477e5a6f98
commit b367776c37
5 changed files with 225 additions and 67 deletions

View File

@@ -12,6 +12,7 @@ Version 200
* Use new buffer traits, remove old unused traits * Use new buffer traits, remove old unused traits
* Optimize for size on buffers_cat preconditions * Optimize for size on buffers_cat preconditions
* Refactor buffers_suffix * Refactor buffers_suffix
* Tidy up flat_buffer tests
API Changes: API Changes:

View File

@@ -253,7 +253,7 @@ reserve(std::size_t n)
if(max_ < n) if(max_ < n)
max_ = n; max_ = n;
if(capacity() < n) if(capacity() < n)
prepare(n - capacity()); prepare(n - size());
} }
template<class Allocator> template<class Allocator>
@@ -293,13 +293,16 @@ basic_flat_buffer<Allocator>::
prepare(std::size_t n) -> prepare(std::size_t n) ->
mutable_buffers_type mutable_buffers_type
{ {
auto const len = size();
if(len > max_ || n > (max_ - len))
BOOST_THROW_EXCEPTION(std::length_error{
"basic_flat_buffer too long"});
if(n <= dist(out_, end_)) if(n <= dist(out_, end_))
{ {
// existing capacity is sufficient // existing capacity is sufficient
last_ = out_ + n; last_ = out_ + n;
return{out_, n}; return{out_, n};
} }
auto const len = size();
if(n <= capacity() - len) if(n <= capacity() - len)
{ {
// after a memmove, // after a memmove,
@@ -311,10 +314,6 @@ prepare(std::size_t n) ->
last_ = out_ + n; last_ = out_ + n;
return {out_, n}; return {out_, n};
} }
// enforce maximum capacity
if(n > max_ - len)
BOOST_THROW_EXCEPTION(std::length_error{
"basic_flat_buffer overflow"});
// allocate a new buffer // allocate a new buffer
auto const new_size = (std::min<std::size_t>)( auto const new_size = (std::min<std::size_t>)(
max_, max_,

View File

@@ -18,7 +18,6 @@
#include <boost/beast/core/type_traits.hpp> #include <boost/beast/core/type_traits.hpp>
#include <boost/beast/core/detail/type_traits.hpp> #include <boost/beast/core/detail/type_traits.hpp>
#include <boost/asio/buffer.hpp> #include <boost/asio/buffer.hpp>
#include <boost/asio/buffers_iterator.hpp>
#include <boost/assert.hpp> #include <boost/assert.hpp>
#include <algorithm> #include <algorithm>
#include <string> #include <string>
@@ -347,6 +346,24 @@ struct is_mutable_dynamic_buffer<T, detail::void_t<decltype(
namespace detail { namespace detail {
template<class MutableBufferSequence>
void
buffers_fill(
MutableBufferSequence const& buffers,
char c)
{
auto const end =
net::buffer_sequence_end(buffers);
for(auto it = net::buffer_sequence_begin(buffers);
it != end; ++it)
{
net::mutable_buffer b(*it);
std::fill(
static_cast<char*>(b.data()),
static_cast<char*>(b.data()) + b.size(), c);
}
}
template<class MutableDynamicBuffer> template<class MutableDynamicBuffer>
void void
test_mutable_dynamic_buffer( test_mutable_dynamic_buffer(
@@ -369,6 +386,11 @@ test_mutable_dynamic_buffer(
net::is_mutable_buffer_sequence<typename net::is_mutable_buffer_sequence<typename
MutableDynamicBuffer::mutable_data_type>::value); MutableDynamicBuffer::mutable_data_type>::value);
BOOST_STATIC_ASSERT(
std::is_convertible<
typename MutableDynamicBuffer::mutable_data_type,
typename MutableDynamicBuffer::const_buffers_type>::value);
string_view src = "Hello, world!"; string_view src = "Hello, world!";
if(src.size() > b0.max_size()) if(src.size() > b0.max_size())
src = {src.data(), b0.max_size()}; src = {src.data(), b0.max_size()};
@@ -377,11 +399,8 @@ test_mutable_dynamic_buffer(
{ {
MutableDynamicBuffer b(b0); MutableDynamicBuffer b(b0);
auto const mb = b.prepare(src.size()); auto const mb = b.prepare(src.size());
using iter_type = net::buffers_iterator<decltype(mb)>;
SUITE_EXPECT(test, buffer_size(mb) == src.size()); SUITE_EXPECT(test, buffer_size(mb) == src.size());
std::fill( buffers_fill(mb, '*');
iter_type::begin(mb),
iter_type::end(mb), '*');
b.commit(src.size()); b.commit(src.size());
SUITE_EXPECT(test, b.size() == src.size()); SUITE_EXPECT(test, b.size() == src.size());
SUITE_EXPECT(test, SUITE_EXPECT(test,

View File

@@ -17,7 +17,6 @@
#include <boost/beast/core/string.hpp> #include <boost/beast/core/string.hpp>
#include <boost/beast/test/test_allocator.hpp> #include <boost/beast/test/test_allocator.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp> #include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/asio/buffers_iterator.hpp>
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
@@ -27,55 +26,16 @@ namespace beast {
class flat_buffer_test : public beast::unit_test::suite class flat_buffer_test : public beast::unit_test::suite
{ {
public: public:
BOOST_STATIC_ASSERT(
net::is_dynamic_buffer<
flat_buffer>::value);
BOOST_STATIC_ASSERT(
net::is_const_buffer_sequence<
flat_buffer::const_buffers_type>::value);
BOOST_STATIC_ASSERT(
net::is_mutable_buffer_sequence<
flat_buffer::mutable_data_type>::value);
BOOST_STATIC_ASSERT(
net::is_mutable_buffer_sequence<
flat_buffer::mutable_buffers_type>::value);
BOOST_STATIC_ASSERT(std::is_convertible<
flat_buffer::mutable_data_type,
flat_buffer::const_buffers_type>::value);
template<class DynamicBuffer>
void void
testMutableData() testDynamicBuffer()
{ {
DynamicBuffer b; flat_buffer b(30);
DynamicBuffer const& cb = b; BEAST_EXPECT(b.max_size() == 30);
ostream(b) << "Hello"; test_dynamic_buffer(*this, b);
BOOST_STATIC_ASSERT(
net::is_const_buffer_sequence<
decltype(cb.data())>::value &&
! net::is_mutable_buffer_sequence<
decltype(cb.data())>::value);
BOOST_STATIC_ASSERT(
net::is_const_buffer_sequence<
decltype(cb.cdata())>::value &&
! net::is_mutable_buffer_sequence<
decltype(cb.cdata())>::value);
BOOST_STATIC_ASSERT(
net::is_mutable_buffer_sequence<
decltype(b.data())>::value);
std::for_each(
net::buffers_iterator<decltype(b.data())>::begin(b.data()),
net::buffers_iterator<decltype(b.data())>::end(b.data()),
[](char& c)
{
c = static_cast<char>(std::toupper(c));
});
BEAST_EXPECT(buffers_to_string(b.data()) == "HELLO");
BEAST_EXPECT(buffers_to_string(b.cdata()) == "HELLO");
} }
void void
testBuffer() testSpecialMembers()
{ {
using namespace test; using namespace test;
@@ -181,6 +141,16 @@ public:
BEAST_EXPECT(buffers_to_string(b1.data()) == "Hello"); BEAST_EXPECT(buffers_to_string(b1.data()) == "Hello");
BEAST_EXPECT(buffers_to_string(b2.data()) == "Hello"); BEAST_EXPECT(buffers_to_string(b2.data()) == "Hello");
} }
{
flat_buffer b1;
ostream(b1) << "Hello";
basic_flat_buffer<a_t> b2;
b2.reserve(1);
BEAST_EXPECT(b2.capacity() == 1);
b2 = b1;
BEAST_EXPECT(buffers_to_string(b2.data()) == "Hello");
BEAST_EXPECT(b2.capacity() == b2.size());
}
// move assignment // move assignment
{ {
@@ -319,6 +289,24 @@ public:
BEAST_EXPECT(b.max_size() == 32); BEAST_EXPECT(b.max_size() == 32);
} }
// allocator max_size
{
basic_flat_buffer<a_t> b;
auto a = b.get_allocator();
BOOST_STATIC_ASSERT(
! std::is_const<decltype(a)>::value);
a->max_size = 30;
try
{
b.prepare(1000);
fail("", __FILE__, __LINE__);
}
catch(std::length_error const&)
{
pass();
}
}
// read_size // read_size
{ {
flat_buffer b{10}; flat_buffer b{10};
@@ -392,6 +380,11 @@ public:
b.commit(20); b.commit(20);
b.reserve(50); b.reserve(50);
BEAST_EXPECT(b.capacity() == 50); BEAST_EXPECT(b.capacity() == 50);
b.max_size(b.capacity());
b.reserve(b.max_size() + 20);
BEAST_EXPECT(b.capacity() == 70);
BEAST_EXPECT(b.max_size() == 70);
} }
// shrink to fit // shrink to fit
@@ -406,14 +399,20 @@ public:
BEAST_EXPECT(b.capacity() >= 125); BEAST_EXPECT(b.capacity() >= 125);
b.shrink_to_fit(); b.shrink_to_fit();
BEAST_EXPECT(b.capacity() == b.size()); BEAST_EXPECT(b.capacity() == b.size());
b.shrink_to_fit();
BEAST_EXPECT(b.capacity() == b.size());
b.consume(b.size());
BEAST_EXPECT(b.size() == 0);
b.shrink_to_fit();
BEAST_EXPECT(b.capacity() == 0);
} }
} }
void void
run() override run() override
{ {
testBuffer(); testDynamicBuffer();
testMutableData<flat_buffer>(); testSpecialMembers();
} }
}; };

View File

@@ -12,6 +12,7 @@
#include <atomic> #include <atomic>
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <memory> #include <memory>
namespace boost { namespace boost {
@@ -26,6 +27,8 @@ struct test_allocator_info
std::size_t nmassign = 0; std::size_t nmassign = 0;
std::size_t ncpassign = 0; std::size_t ncpassign = 0;
std::size_t nselect = 0; std::size_t nselect = 0;
std::size_t max_size = (
std::numeric_limits<std::size_t>::max)();
test_allocator_info() test_allocator_info()
: id([] : id([]
@@ -37,28 +40,55 @@ struct test_allocator_info
} }
}; };
template<class T, bool Equal, bool Assign, bool Move, bool Swap, bool Select> template<
class T,
bool Equal,
bool Assign,
bool Move,
bool Swap,
bool Select>
class test_allocator; class test_allocator;
template<class T, bool Equal, bool Assign, bool Move, bool Swap, bool Select> template<
class T,
bool Equal,
bool Assign,
bool Move,
bool Swap,
bool Select>
struct test_allocator_base struct test_allocator_base
{ {
}; };
template<class T, bool Equal, bool Assign, bool Move, bool Swap> // Select == true
struct test_allocator_base<T, Equal, Assign, Move, Swap, true> template<
class T,
bool Equal,
bool Assign,
bool Move,
bool Swap>
struct test_allocator_base<
T, Equal, Assign, Move, Swap, true>
{ {
static static
test_allocator<T, Equal, Assign, Move, Swap, true> test_allocator<T, Equal, Assign, Move, Swap, true>
select_on_container_copy_construction(test_allocator< select_on_container_copy_construction(test_allocator<
T, Equal, Assign, Move, Swap, true> const&) T, Equal, Assign, Move, Swap, true> const&)
{ {
return test_allocator<T, Equal, Assign, Move, Swap, true>{}; return test_allocator<T,
Equal, Assign, Move, Swap, true>{};
} }
}; };
template<class T, bool Equal, bool Assign, bool Move, bool Swap, bool Select> template<
class test_allocator : public test_allocator_base< class T,
bool Equal,
bool Assign,
bool Move,
bool Swap,
bool Select>
class test_allocator
: public test_allocator_base<
T, Equal, Assign, Move, Swap, Select> T, Equal, Assign, Move, Swap, Select>
{ {
std::shared_ptr<test_allocator_info> info_; std::shared_ptr<test_allocator_info> info_;
@@ -85,7 +115,8 @@ public:
}; };
test_allocator() test_allocator()
: info_(std::make_shared<test_allocator_info>()) : info_(std::make_shared<
test_allocator_info>())
{ {
} }
@@ -138,6 +169,18 @@ public:
::operator delete(p); ::operator delete(p);
} }
std::size_t
max_size() const
{
return info_->max_size;
}
void
max_size(std::size_t n)
{
info_->max_size = n;
}
bool bool
operator==(test_allocator const& other) const operator==(test_allocator const& other) const
{ {
@@ -156,13 +199,110 @@ public:
return info_->id; return info_->id;
} }
test_allocator_info const* test_allocator_info*
operator->() const operator->() const
{ {
return info_.get(); return info_.get();
} }
}; };
//------------------------------------------------------------------------------
#if 0
struct allocator_info
{
std::size_t const id;
allocator_info()
: id([]
{
static std::atomic<std::size_t> sid(0);
return ++sid;
}())
{
}
};
struct allocator_defaults
{
static std::size_t constexpr max_size =
(std::numeric_limits<std::size_t>::max)();
};
template<
class T,
class Traits = allocator_defaults>
struct allocator
{
public:
using value_type = T;
#if 0
template<class U>
struct rebind
{
using other =
test_allocator<U, Traits>;
};
#endif
allocator() = default;
allocator(allocator&& t) = default;
allocator(allocator const& u) = default;
template<
class U,
class = typename std::enable_if<
! std::is_same<U, T>::value>::type>
allocator(
allocator<U, Traits> const& u) noexcept
{
}
allocator&
operator=(allocator&& u) noexcept
{
return *this;
}
allocator&
operator=(allocator const& u) noexcept
{
return *this;
}
value_type*
allocate(std::size_t n)
{
return static_cast<value_type*>(
::operator new(n * sizeof(value_type)));
}
void
deallocate(value_type* p, std::size_t) noexcept
{
::operator delete(p);
}
std::size_t
max_size() const
{
}
bool
operator==(test_allocator const& other) const
{
return id() == other.id() || Equal;
}
bool
operator!=(test_allocator const& other) const
{
return ! this->operator==(other);
}
};
#endif
} // test } // test
} // beast } // beast
} // boost } // boost