diff --git a/test/utils/allocator.hpp b/test/utils/allocator.hpp new file mode 100644 index 00000000..d6b24ccb --- /dev/null +++ b/test/utils/allocator.hpp @@ -0,0 +1,42 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_DETAIL_ALLOCATOR_HPP +#define BOOST_BEAST_DETAIL_ALLOCATOR_HPP + +#include +#ifdef BOOST_NO_CXX11_ALLOCATOR +#include +#else +#include +#endif + +namespace boost { +namespace beast { +namespace detail { + +// This is a workaround for allocator_traits +// implementations which falsely claim C++11 +// compatibility. + +#ifdef BOOST_NO_CXX11_ALLOCATOR +template +using allocator_traits = boost::container::allocator_traits; + +#else +template +using allocator_traits = std::allocator_traits; + +#endif + +} // detail +} // beast +} // boost + +#endif diff --git a/test/utils/empty_value.hpp b/test/utils/empty_value.hpp new file mode 100644 index 00000000..7139b3e1 --- /dev/null +++ b/test/utils/empty_value.hpp @@ -0,0 +1,144 @@ +/* +Copyright 2018 Glen Joseph Fernandes +(glenjofe@gmail.com) + +Distributed under the Boost Software License, Version 1.0. +(http://www.boost.org/LICENSE_1_0.txt) +*/ +#ifndef BOOST_CORE_EMPTY_VALUE_HPP +#define BOOST_CORE_EMPTY_VALUE_HPP + +#include +#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) +#include +#endif + +#if defined(BOOST_GCC_VERSION) && (BOOST_GCC_VERSION >= 40700) +#define BOOST_DETAIL_EMPTY_VALUE_BASE +#elif defined(BOOST_INTEL) && defined(_MSC_VER) && (_MSC_VER >= 1800) +#define BOOST_DETAIL_EMPTY_VALUE_BASE +#elif defined(BOOST_MSVC) && (BOOST_MSVC >= 1800) +#define BOOST_DETAIL_EMPTY_VALUE_BASE +#elif defined(BOOST_CLANG) && !defined(__CUDACC__) +#if __has_feature(is_empty) && __has_feature(is_final) +#define BOOST_DETAIL_EMPTY_VALUE_BASE +#endif +#endif + +namespace boost { + +template +struct use_empty_value_base { + enum { +#if defined(BOOST_DETAIL_EMPTY_VALUE_BASE) + value = __is_empty(T) && !__is_final(T) +#else + value = false +#endif + }; +}; + +struct empty_init_t { }; + +namespace empty_ { + +template::value> +class empty_value { +public: + typedef T type; + +#if !defined(BOOST_NO_CXX11_DEFAULTED_FUNCTIONS) + empty_value() = default; +#else + empty_value() { } +#endif + + empty_value(boost::empty_init_t) + : value_() { } + +#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + template + explicit empty_value(boost::empty_init_t, Args&&... args) + : value_(std::forward(args)...) { } +#else + template + empty_value(boost::empty_init_t, U&& value) + : value_(std::forward(value)) { } +#endif +#else + template + empty_value(boost::empty_init_t, const U& value) + : value_(value) { } + + template + empty_value(boost::empty_init_t, U& value) + : value_(value) { } +#endif + + const T& get() const BOOST_NOEXCEPT { + return value_; + } + + T& get() BOOST_NOEXCEPT { + return value_; + } + +private: + T value_; +}; + +#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) +template +class empty_value + : T { +public: + typedef T type; + +#if !defined(BOOST_NO_CXX11_DEFAULTED_FUNCTIONS) + empty_value() = default; +#else + empty_value() { } +#endif + + empty_value(boost::empty_init_t) + : T() { } + +#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + template + explicit empty_value(boost::empty_init_t, Args&&... args) + : T(std::forward(args)...) { } +#else + template + empty_value(boost::empty_init_t, U&& value) + : T(std::forward(value)) { } +#endif +#else + template + empty_value(boost::empty_init_t, const U& value) + : T(value) { } + + template + empty_value(boost::empty_init_t, U& value) + : T(value) { } +#endif + + const T& get() const BOOST_NOEXCEPT { + return *this; + } + + T& get() BOOST_NOEXCEPT { + return *this; + } +}; +#endif + +} /* empty_ */ + +using empty_::empty_value; + +} /* boost */ + +#endif diff --git a/test/utils/error.hpp b/test/utils/error.hpp new file mode 100644 index 00000000..e857999a --- /dev/null +++ b/test/utils/error.hpp @@ -0,0 +1,40 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_TEST_ERROR_HPP +#define BOOST_BEAST_TEST_ERROR_HPP + +#include +#include + +namespace boost { +namespace beast { +namespace test { + +/// Error codes returned from unit testing algorithms +enum class error +{ + /** The test stream generated a simulated testing error + + This error is returned by a @ref fail_count object + when it generates a simulated error. + */ + test_failure = 1 +}; + +} // test +} // beast +} // boost + +#include +#ifdef BOOST_BEAST_HEADER_ONLY +#include +#endif + +#endif diff --git a/test/utils/fail_count.hpp b/test/utils/fail_count.hpp new file mode 100644 index 00000000..f5683191 --- /dev/null +++ b/test/utils/fail_count.hpp @@ -0,0 +1,70 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_TEST_FAIL_COUNT_HPP +#define BOOST_BEAST_TEST_FAIL_COUNT_HPP + +#include +#include +#include + +namespace boost { +namespace beast { +namespace test { + +/** A countdown to simulated failure + + On the Nth operation, the class will fail with the specified + error code, or the default error code of @ref error::test_failure. + + Instances of this class may be used to build objects which + are specifically designed to aid in writing unit tests, for + interfaces which can throw exceptions or return `error_code` + values representing failure. +*/ +class fail_count +{ + std::size_t n_; + std::size_t i_ = 0; + error_code ec_; + +public: + fail_count(fail_count&&) = default; + + /** Construct a counter + + @param n The 0-based index of the operation to fail on or after + @param ev An optional error code to use when generating a simulated failure + */ + BOOST_BEAST_DECL + explicit + fail_count( + std::size_t n, + error_code ev = error::test_failure); + + /// Throw an exception on the Nth failure + BOOST_BEAST_DECL + void + fail(); + + /// Set an error code on the Nth failure + BOOST_BEAST_DECL + bool + fail(error_code& ec); +}; + +} // test +} // beast +} // boost + +#ifdef BOOST_BEAST_HEADER_ONLY +#include +#endif + +#endif diff --git a/test/utils/flat_buffer.hpp b/test/utils/flat_buffer.hpp new file mode 100644 index 00000000..9e8d639c --- /dev/null +++ b/test/utils/flat_buffer.hpp @@ -0,0 +1,533 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_FLAT_BUFFER_HPP +#define BOOST_BEAST_FLAT_BUFFER_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { + +/** A dynamic buffer providing buffer sequences of length one. + + A dynamic buffer encapsulates memory storage that may be + automatically resized as required, where the memory is + divided into two regions: readable bytes followed by + writable bytes. These memory regions are internal to + the dynamic buffer, but direct access to the elements + is provided to permit them to be efficiently used with + I/O operations. + + Objects of this type meet the requirements of DynamicBuffer + and have the following additional properties: + + @li A mutable buffer sequence representing the readable + bytes is returned by @ref data when `this` is non-const. + + @li A configurable maximum buffer size may be set upon + construction. Attempts to exceed the buffer size will throw + `std::length_error`. + + @li Buffer sequences representing the readable and writable + bytes, returned by @ref data and @ref prepare, will have + length one. + + Upon construction, a maximum size for the buffer may be + specified. If this limit is exceeded, the `std::length_error` + exception will be thrown. + + @note This class is designed for use with algorithms that + take dynamic buffers as parameters, and are optimized + for the case where the input sequence or output sequence + is stored in a single contiguous buffer. +*/ +template +class basic_flat_buffer +#if ! BOOST_BEAST_DOXYGEN + : private boost::empty_value< + typename detail::allocator_traits:: + template rebind_alloc> +#endif +{ + template + friend class basic_flat_buffer; + + using base_alloc_type = typename + detail::allocator_traits:: + template rebind_alloc; + + static bool constexpr default_nothrow = + std::is_nothrow_default_constructible::value; + + using alloc_traits = + beast::detail::allocator_traits; + + using pocma = typename + alloc_traits::propagate_on_container_move_assignment; + + using pocca = typename + alloc_traits::propagate_on_container_copy_assignment; + + static + std::size_t + dist(char const* first, char const* last) noexcept + { + return static_cast(last - first); + } + + char* begin_; + char* in_; + char* out_; + char* last_; + char* end_; + std::size_t max_; + +public: + /// The type of allocator used. + using allocator_type = Allocator; + + /// Destructor + ~basic_flat_buffer(); + + /** Constructor + + After construction, @ref capacity will return zero, and + @ref max_size will return the largest value which may + be passed to the allocator's `allocate` function. + */ + basic_flat_buffer() noexcept(default_nothrow); + + /** Constructor + + After construction, @ref capacity will return zero, and + @ref max_size will return the specified value of `limit`. + + @param limit The desired maximum size. + */ + explicit + basic_flat_buffer( + std::size_t limit) noexcept(default_nothrow); + + /** Constructor + + After construction, @ref capacity will return zero, and + @ref max_size will return the largest value which may + be passed to the allocator's `allocate` function. + + @param alloc The allocator to use for the object. + + @esafe + + No-throw guarantee. + */ + explicit + basic_flat_buffer(Allocator const& alloc) noexcept; + + /** Constructor + + After construction, @ref capacity will return zero, and + @ref max_size will return the specified value of `limit`. + + @param limit The desired maximum size. + + @param alloc The allocator to use for the object. + + @esafe + + No-throw guarantee. + */ + basic_flat_buffer( + std::size_t limit, + Allocator const& alloc) noexcept; + + /** Move Constructor + + The container is constructed with the contents of `other` + using move semantics. The maximum size will be the same + as the moved-from object. + + Buffer sequences previously obtained from `other` using + @ref data or @ref prepare remain valid after the move. + + @param other The object to move from. After the move, the + moved-from object will have zero capacity, zero readable + bytes, and zero writable bytes. + + @esafe + + No-throw guarantee. + */ + basic_flat_buffer(basic_flat_buffer&& other) noexcept; + + /** Move Constructor + + Using `alloc` as the allocator for the new container, the + contents of `other` are moved. If `alloc != other.get_allocator()`, + this results in a copy. The maximum size will be the same + as the moved-from object. + + Buffer sequences previously obtained from `other` using + @ref data or @ref prepare become invalid after the move. + + @param other The object to move from. After the move, + the moved-from object will have zero capacity, zero readable + bytes, and zero writable bytes. + + @param alloc The allocator to use for the object. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of `alloc`. + */ + basic_flat_buffer( + basic_flat_buffer&& other, + Allocator const& alloc); + + /** Copy Constructor + + This container is constructed with the contents of `other` + using copy semantics. The maximum size will be the same + as the copied object. + + @param other The object to copy from. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of the allocator. + */ + basic_flat_buffer(basic_flat_buffer const& other); + + /** Copy Constructor + + This container is constructed with the contents of `other` + using copy semantics and the specified allocator. The maximum + size will be the same as the copied object. + + @param other The object to copy from. + + @param alloc The allocator to use for the object. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of `alloc`. + */ + basic_flat_buffer( + basic_flat_buffer const& other, + Allocator const& alloc); + + /** Copy Constructor + + This container is constructed with the contents of `other` + using copy semantics. The maximum size will be the same + as the copied object. + + @param other The object to copy from. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of the allocator. + */ + template + basic_flat_buffer( + basic_flat_buffer const& other) + noexcept(default_nothrow); + + /** Copy Constructor + + This container is constructed with the contents of `other` + using copy semantics. The maximum size will be the same + as the copied object. + + @param other The object to copy from. + + @param alloc The allocator to use for the object. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of `alloc`. + */ + template + basic_flat_buffer( + basic_flat_buffer const& other, + Allocator const& alloc); + + /** Move Assignment + + The container is assigned with the contents of `other` + using move semantics. The maximum size will be the same + as the moved-from object. + + Buffer sequences previously obtained from `other` using + @ref data or @ref prepare remain valid after the move. + + @param other The object to move from. After the move, + the moved-from object will have zero capacity, zero readable + bytes, and zero writable bytes. + + @esafe + + No-throw guarantee. + */ + basic_flat_buffer& + operator=(basic_flat_buffer&& other) noexcept; + + /** Copy Assignment + + The container is assigned with the contents of `other` + using copy semantics. The maximum size will be the same + as the copied object. + + After the copy, `this` will have zero writable bytes. + + @param other The object to copy from. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of the allocator. + */ + basic_flat_buffer& + operator=(basic_flat_buffer const& other); + + /** Copy assignment + + The container is assigned with the contents of `other` + using copy semantics. The maximum size will be the same + as the copied object. + + After the copy, `this` will have zero writable bytes. + + @param other The object to copy from. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of the allocator. + */ + template + basic_flat_buffer& + operator=(basic_flat_buffer const& other); + + /// Returns a copy of the allocator used. + allocator_type + get_allocator() const + { + return this->get(); + } + + /** Set the maximum allowed capacity + + This function changes the currently configured upper limit + on capacity to the specified value. + + @param n The maximum number of bytes ever allowed for capacity. + + @esafe + + No-throw guarantee. + */ + void + max_size(std::size_t n) noexcept + { + max_ = n; + } + + /** Guarantee a minimum capacity + + This function adjusts the internal storage (if necessary) + to guarantee space for at least `n` bytes. + + Buffer sequences previously obtained using @ref data or + @ref prepare become invalid. + + @param n The minimum number of byte for the new capacity. + If this value is greater than the maximum size, then the + maximum size will be adjusted upwards to this value. + + @esafe + + Basic guarantee. + + @throws std::length_error if n is larger than the maximum + allocation size of the allocator. + */ + void + reserve(std::size_t n); + + /** Reallocate the buffer to fit the readable bytes exactly. + + Buffer sequences previously obtained using @ref data or + @ref prepare become invalid. + + @esafe + + Strong guarantee. + */ + void + shrink_to_fit(); + + /** Set the size of the readable and writable bytes to zero. + + This clears the buffer without changing capacity. + Buffer sequences previously obtained using @ref data or + @ref prepare become invalid. + + @esafe + + No-throw guarantee. + */ + void + clear() noexcept; + + /// Exchange two dynamic buffers + template + friend + void + swap( + basic_flat_buffer&, + basic_flat_buffer&); + + //-------------------------------------------------------------------------- + + /// The ConstBufferSequence used to represent the readable bytes. + using const_buffers_type = net::const_buffer; + + /// The MutableBufferSequence used to represent the readable bytes. + using mutable_data_type = net::mutable_buffer; + + /// The MutableBufferSequence used to represent the writable bytes. + using mutable_buffers_type = net::mutable_buffer; + + /// Returns the number of readable bytes. + std::size_t + size() const noexcept + { + return dist(in_, out_); + } + + /// Return the maximum number of bytes, both readable and writable, that can ever be held. + std::size_t + max_size() const noexcept + { + return max_; + } + + /// Return the maximum number of bytes, both readable and writable, that can be held without requiring an allocation. + std::size_t + capacity() const noexcept + { + return dist(begin_, end_); + } + + /// Returns a constant buffer sequence representing the readable bytes + const_buffers_type + data() const noexcept + { + return {in_, dist(in_, out_)}; + } + + /// Returns a constant buffer sequence representing the readable bytes + const_buffers_type + cdata() const noexcept + { + return data(); + } + + /// Returns a mutable buffer sequence representing the readable bytes + mutable_data_type + data() noexcept + { + return {in_, dist(in_, out_)}; + } + + /** Returns a mutable buffer sequence representing writable bytes. + + Returns a mutable buffer sequence representing the writable + bytes containing exactly `n` bytes of storage. Memory may be + reallocated as needed. + + All buffers sequences previously obtained using + @ref data or @ref prepare become invalid. + + @param n The desired number of bytes in the returned buffer + sequence. + + @throws std::length_error if `size() + n` exceeds either + `max_size()` or the allocator's maximum allocation size. + + @esafe + + Strong guarantee. + */ + mutable_buffers_type + prepare(std::size_t n); + + /** Append writable bytes to the readable bytes. + + Appends n bytes from the start of the writable bytes to the + end of the readable bytes. The remainder of the writable bytes + are discarded. If n is greater than the number of writable + bytes, all writable bytes are appended to the readable bytes. + + All buffers sequences previously obtained using + @ref data or @ref prepare become invalid. + + @param n The number of bytes to append. If this number + is greater than the number of writable bytes, all + writable bytes are appended. + + @esafe + + No-throw guarantee. + */ + void + commit(std::size_t n) noexcept + { + out_ += (std::min)(n, dist(out_, last_)); + } + + /** Remove bytes from beginning of the readable bytes. + + Removes n bytes from the beginning of the readable bytes. + + All buffers sequences previously obtained using + @ref data or @ref prepare become invalid. + + @param n The number of bytes to remove. If this number + is greater than the number of readable bytes, all + readable bytes are removed. + + @esafe + + No-throw guarantee. + */ + void + consume(std::size_t n) noexcept; + +private: + template + void copy_from(basic_flat_buffer const& other); + void move_assign(basic_flat_buffer&, std::true_type); + void move_assign(basic_flat_buffer&, std::false_type); + void copy_assign(basic_flat_buffer const&, std::true_type); + void copy_assign(basic_flat_buffer const&, std::false_type); + void swap(basic_flat_buffer&); + void swap(basic_flat_buffer&, std::true_type); + void swap(basic_flat_buffer&, std::false_type); + char* alloc(std::size_t n); +}; + +/// A flat buffer which uses the default allocator. +using flat_buffer = + basic_flat_buffer>; + +} // beast +} // boost + +#include + +#endif diff --git a/test/utils/impl/error.hpp b/test/utils/impl/error.hpp new file mode 100644 index 00000000..e9c84795 --- /dev/null +++ b/test/utils/impl/error.hpp @@ -0,0 +1,40 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_TEST_IMPL_ERROR_HPP +#define BOOST_BEAST_TEST_IMPL_ERROR_HPP + +#include +#include +#include + +namespace boost { +namespace system { +template<> +struct is_error_code_enum< + boost::beast::test::error> + : std::true_type +{ +}; +} // system +} // boost + +namespace boost { +namespace beast { +namespace test { + +BOOST_BEAST_DECL +error_code +make_error_code(error e) noexcept; + +} // test +} // beast +} // boost + +#endif diff --git a/test/utils/impl/error.ipp b/test/utils/impl/error.ipp new file mode 100644 index 00000000..f4447137 --- /dev/null +++ b/test/utils/impl/error.ipp @@ -0,0 +1,65 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_TEST_IMPL_ERROR_IPP +#define BOOST_BEAST_TEST_IMPL_ERROR_IPP + +#include + +namespace boost { +namespace beast { +namespace test { + +namespace detail { + +class error_codes : public error_category +{ +public: + BOOST_BEAST_DECL + const char* + name() const noexcept override + { + return "boost.beast.test"; + } + + BOOST_BEAST_DECL + std::string + message(int ev) const override + { + switch(static_cast(ev)) + { + default: + case error::test_failure: return + "An automatic unit test failure occurred"; + } + } + + BOOST_BEAST_DECL + error_condition + default_error_condition(int ev) const noexcept override + { + return error_condition{ev, *this}; + } +}; + +} // detail + +error_code +make_error_code(error e) noexcept +{ + static detail::error_codes const cat{}; + return error_code{static_cast< + std::underlying_type::type>(e), cat}; +} + +} // test +} // beast +} // boost + +#endif diff --git a/test/utils/impl/fail_count.ipp b/test/utils/impl/fail_count.ipp new file mode 100644 index 00000000..c0c25642 --- /dev/null +++ b/test/utils/impl/fail_count.ipp @@ -0,0 +1,58 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_TEST_IMPL_FAIL_COUNT_IPP +#define BOOST_BEAST_TEST_IMPL_FAIL_COUNT_IPP + +#include +#include + +namespace boost { +namespace beast { +namespace test { + +fail_count:: +fail_count( + std::size_t n, + error_code ev) + : n_(n) + , ec_(ev) +{ +} + +void +fail_count:: +fail() +{ + if(i_ < n_) + ++i_; + if(i_ == n_) + BOOST_THROW_EXCEPTION(system_error{ec_}); +} + +bool +fail_count:: +fail(error_code& ec) +{ + if(i_ < n_) + ++i_; + if(i_ == n_) + { + ec = ec_; + return true; + } + ec = {}; + return false; +} + +} // test +} // beast +} // boost + +#endif diff --git a/test/utils/impl/flat_buffer.hpp b/test/utils/impl/flat_buffer.hpp new file mode 100644 index 00000000..83d3b4e7 --- /dev/null +++ b/test/utils/impl/flat_buffer.hpp @@ -0,0 +1,533 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_IMPL_FLAT_BUFFER_HPP +#define BOOST_BEAST_IMPL_FLAT_BUFFER_HPP + +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { + +/* Layout: + + begin_ in_ out_ last_ end_ + |<------->|<---------->|<---------->|<------->| + | readable | writable | +*/ + +template +basic_flat_buffer:: +~basic_flat_buffer() +{ + if(! begin_) + return; + alloc_traits::deallocate( + this->get(), begin_, capacity()); +} + +template +basic_flat_buffer:: +basic_flat_buffer() noexcept(default_nothrow) + : begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(alloc_traits::max_size( + this->get())) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer( + std::size_t limit) noexcept(default_nothrow) + : begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(limit) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer(Allocator const& alloc) noexcept + : boost::empty_value( + boost::empty_init_t{}, alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(alloc_traits::max_size( + this->get())) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer( + std::size_t limit, + Allocator const& alloc) noexcept + : boost::empty_value( + boost::empty_init_t{}, alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(limit) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer(basic_flat_buffer&& other) noexcept + : boost::empty_value( + boost::empty_init_t{}, std::move(other.get())) + , begin_(boost::exchange(other.begin_, nullptr)) + , in_(boost::exchange(other.in_, nullptr)) + , out_(boost::exchange(other.out_, nullptr)) + , last_(boost::exchange(other.last_, nullptr)) + , end_(boost::exchange(other.end_, nullptr)) + , max_(other.max_) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer( + basic_flat_buffer&& other, + Allocator const& alloc) + : boost::empty_value( + boost::empty_init_t{}, alloc) +{ + if(this->get() != other.get()) + { + begin_ = nullptr; + in_ = nullptr; + out_ = nullptr; + last_ = nullptr; + end_ = nullptr; + max_ = other.max_; + copy_from(other); + other.clear(); + other.shrink_to_fit(); + return; + } + + begin_ = other.begin_; + in_ = other.in_; + out_ = other.out_; + last_ = other.out_; // invalidate + end_ = other.end_; + max_ = other.max_; + BOOST_ASSERT( + alloc_traits::max_size(this->get()) == + alloc_traits::max_size(other.get())); + other.begin_ = nullptr; + other.in_ = nullptr; + other.out_ = nullptr; + other.last_ = nullptr; + other.end_ = nullptr; +} + +template +basic_flat_buffer:: +basic_flat_buffer(basic_flat_buffer const& other) + : boost::empty_value(boost::empty_init_t{}, + alloc_traits::select_on_container_copy_construction( + other.get())) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(other.max_) +{ + copy_from(other); +} + +template +basic_flat_buffer:: +basic_flat_buffer( + basic_flat_buffer const& other, + Allocator const& alloc) + : boost::empty_value( + boost::empty_init_t{}, alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(other.max_) +{ + copy_from(other); +} + +template +template +basic_flat_buffer:: +basic_flat_buffer( + basic_flat_buffer const& other) + noexcept(default_nothrow) + : begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(other.max_) +{ + copy_from(other); +} + +template +template +basic_flat_buffer:: +basic_flat_buffer( + basic_flat_buffer const& other, + Allocator const& alloc) + : boost::empty_value( + boost::empty_init_t{}, alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(other.max_) +{ + copy_from(other); +} + +template +auto +basic_flat_buffer:: +operator=(basic_flat_buffer&& other) noexcept -> + basic_flat_buffer& +{ + if(this == &other) + return *this; + move_assign(other, pocma{}); + return *this; +} + +template +auto +basic_flat_buffer:: +operator=(basic_flat_buffer const& other) -> + basic_flat_buffer& +{ + if(this == &other) + return *this; + copy_assign(other, pocca{}); + return *this; +} + +template +template +auto +basic_flat_buffer:: +operator=( + basic_flat_buffer const& other) -> + basic_flat_buffer& +{ + copy_from(other); + return *this; +} + +template +void +basic_flat_buffer:: +reserve(std::size_t n) +{ + if(max_ < n) + max_ = n; + if(n > capacity()) + prepare(n - size()); +} + +template +void +basic_flat_buffer:: +shrink_to_fit() +{ + auto const len = size(); + if(len == capacity()) + return; + char* p; + if(len > 0) + { + BOOST_ASSERT(begin_); + BOOST_ASSERT(in_); + p = alloc(len); + std::memcpy(p, in_, len); + } + else + { + p = nullptr; + } + alloc_traits::deallocate( + this->get(), begin_, this->capacity()); + begin_ = p; + in_ = begin_; + out_ = begin_ + len; + last_ = out_; + end_ = out_; +} + +template +void +basic_flat_buffer:: +clear() noexcept +{ + in_ = begin_; + out_ = begin_; + last_ = begin_; +} + +//------------------------------------------------------------------------------ + +template +auto +basic_flat_buffer:: +prepare(std::size_t n) -> + 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_)) + { + // existing capacity is sufficient + last_ = out_ + n; + return{out_, n}; + } + if(n <= capacity() - len) + { + // after a memmove, + // existing capacity is sufficient + if(len > 0) + { + BOOST_ASSERT(begin_); + BOOST_ASSERT(in_); + std::memmove(begin_, in_, len); + } + in_ = begin_; + out_ = in_ + len; + last_ = out_ + n; + return {out_, n}; + } + // allocate a new buffer + auto const new_size = (std::min)( + max_, + (std::max)(2 * len, len + n)); + auto const p = alloc(new_size); + if(begin_) + { + BOOST_ASSERT(p); + BOOST_ASSERT(in_); + std::memcpy(p, in_, len); + alloc_traits::deallocate( + this->get(), begin_, capacity()); + } + begin_ = p; + in_ = begin_; + out_ = in_ + len; + last_ = out_ + n; + end_ = begin_ + new_size; + return {out_, n}; +} + +template +void +basic_flat_buffer:: +consume(std::size_t n) noexcept +{ + if(n >= dist(in_, out_)) + { + in_ = begin_; + out_ = begin_; + return; + } + in_ += n; +} + +//------------------------------------------------------------------------------ + +template +template +void +basic_flat_buffer:: +copy_from( + basic_flat_buffer const& other) +{ + std::size_t const n = other.size(); + if(n == 0 || n > capacity()) + { + if(begin_ != nullptr) + { + alloc_traits::deallocate( + this->get(), begin_, + this->capacity()); + begin_ = nullptr; + in_ = nullptr; + out_ = nullptr; + last_ = nullptr; + end_ = nullptr; + } + if(n == 0) + return; + begin_ = alloc(n); + in_ = begin_; + out_ = begin_ + n; + last_ = begin_ + n; + end_ = begin_ + n; + } + in_ = begin_; + out_ = begin_ + n; + last_ = begin_ + n; + if(begin_) + { + BOOST_ASSERT(other.begin_); + std::memcpy(begin_, other.in_, n); + } +} + +template +void +basic_flat_buffer:: +move_assign(basic_flat_buffer& other, std::true_type) +{ + clear(); + shrink_to_fit(); + this->get() = std::move(other.get()); + begin_ = other.begin_; + in_ = other.in_; + out_ = other.out_; + last_ = out_; + end_ = other.end_; + max_ = other.max_; + other.begin_ = nullptr; + other.in_ = nullptr; + other.out_ = nullptr; + other.last_ = nullptr; + other.end_ = nullptr; +} + +template +void +basic_flat_buffer:: +move_assign(basic_flat_buffer& other, std::false_type) +{ + if(this->get() != other.get()) + { + copy_from(other); + other.clear(); + other.shrink_to_fit(); + } + else + { + move_assign(other, std::true_type{}); + } +} + +template +void +basic_flat_buffer:: +copy_assign(basic_flat_buffer const& other, std::true_type) +{ + max_ = other.max_; + this->get() = other.get(); + copy_from(other); +} + +template +void +basic_flat_buffer:: +copy_assign(basic_flat_buffer const& other, std::false_type) +{ + clear(); + shrink_to_fit(); + max_ = other.max_; + copy_from(other); +} + +template +void +basic_flat_buffer:: +swap(basic_flat_buffer& other) +{ + swap(other, typename + alloc_traits::propagate_on_container_swap{}); +} + +template +void +basic_flat_buffer:: +swap(basic_flat_buffer& other, std::true_type) +{ + using std::swap; + swap(this->get(), other.get()); + swap(max_, other.max_); + swap(begin_, other.begin_); + swap(in_, other.in_); + swap(out_, other.out_); + last_ = this->out_; + other.last_ = other.out_; + swap(end_, other.end_); +} + +template +void +basic_flat_buffer:: +swap(basic_flat_buffer& other, std::false_type) +{ + BOOST_ASSERT(this->get() == other.get()); + using std::swap; + swap(max_, other.max_); + swap(begin_, other.begin_); + swap(in_, other.in_); + swap(out_, other.out_); + last_ = this->out_; + other.last_ = other.out_; + swap(end_, other.end_); +} + +template +void +swap( + basic_flat_buffer& lhs, + basic_flat_buffer& rhs) +{ + lhs.swap(rhs); +} + +template +char* +basic_flat_buffer:: +alloc(std::size_t n) +{ + if(n > alloc_traits::max_size(this->get())) + BOOST_THROW_EXCEPTION(std::length_error( + "A basic_flat_buffer exceeded the allocator's maximum size")); + return alloc_traits::allocate(this->get(), n); +} + +} // beast +} // boost + +#endif diff --git a/test/utils/impl/is_invocable.hpp b/test/utils/impl/is_invocable.hpp new file mode 100644 index 00000000..65f1b6c8 --- /dev/null +++ b/test/utils/impl/is_invocable.hpp @@ -0,0 +1,58 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_DETAIL_IS_INVOCABLE_HPP +#define BOOST_BEAST_DETAIL_IS_INVOCABLE_HPP + +#include +#include + +namespace boost { +namespace beast { +namespace detail { + +template +auto +is_invocable_test(C&& c, int, A&& ...a) + -> decltype(std::is_convertible< + decltype(c(std::forward(a)...)), R>::value || + std::is_same::value, + std::true_type()); + +template +std::false_type +is_invocable_test(C&& c, long, A&& ...a); + +/** Metafunction returns `true` if F callable as R(A...) + + Example: + + @code + is_invocable::value + @endcode +*/ +/** @{ */ +template +struct is_invocable : std::false_type +{ +}; + +template +struct is_invocable + : decltype(is_invocable_test( + std::declval(), 1, std::declval()...)) +{ +}; +/** @} */ + +} // detail +} // beast +} // boost + +#endif diff --git a/test/utils/impl/stream.hpp b/test/utils/impl/stream.hpp new file mode 100644 index 00000000..3925d742 --- /dev/null +++ b/test/utils/impl/stream.hpp @@ -0,0 +1,456 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_TEST_IMPL_STREAM_HPP +#define BOOST_BEAST_TEST_IMPL_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace test { + +//------------------------------------------------------------------------------ + +struct stream::service_impl +{ + std::mutex m_; + std::vector v_; + + BOOST_BEAST_DECL + void + remove(state& impl); +}; + +class stream::service + : public beast::detail::service_base +{ + boost::shared_ptr sp_; + + BOOST_BEAST_DECL + void + shutdown() override; + +public: + BOOST_BEAST_DECL + explicit + service(net::execution_context& ctx); + + BOOST_BEAST_DECL + static + auto + make_impl( + net::io_context& ctx, + test::fail_count* fc) -> + boost::shared_ptr; +}; + +//------------------------------------------------------------------------------ + +template +class stream::read_op : public stream::read_op_base +{ + using ex1_type = + net::io_context::executor_type; + using ex2_type + = net::associated_executor_t; + + struct lambda + { + Handler h_; + boost::weak_ptr wp_; + Buffers b_; + net::executor_work_guard wg2_; + + lambda(lambda&&) = default; + lambda(lambda const&) = default; + + template + lambda( + Handler_&& h, + boost::shared_ptr const& s, + Buffers const& b) + : h_(std::forward(h)) + , wp_(s) + , b_(b) + , wg2_(net::get_associated_executor( + h_, s->ioc.get_executor())) + { + } + + void + operator()(error_code ec) + { + std::size_t bytes_transferred = 0; + auto sp = wp_.lock(); + if(! sp) + ec = net::error::operation_aborted; + if(! ec) + { + std::lock_guard lock(sp->m); + BOOST_ASSERT(! sp->op); + if(sp->b.size() > 0) + { + bytes_transferred = + net::buffer_copy( + b_, sp->b.data(), sp->read_max); + sp->b.consume(bytes_transferred); + sp->nread_bytes += bytes_transferred; + } + else if (buffer_bytes(b_) > 0) + { + ec = net::error::eof; + } + } + + auto alloc = net::get_associated_allocator(h_); + wg2_.get_executor().dispatch( + beast::bind_front_handler(std::move(h_), + ec, bytes_transferred), alloc); + wg2_.reset(); + } + }; + + lambda fn_; + net::executor_work_guard wg1_; + +public: + template + read_op( + Handler_&& h, + boost::shared_ptr const& s, + Buffers const& b) + : fn_(std::forward(h), s, b) + , wg1_(s->ioc.get_executor()) + { + } + + void + operator()(error_code ec) override + { + + auto alloc = net::get_associated_allocator(fn_.h_); + wg1_.get_executor().post( + beast::bind_front_handler(std::move(fn_), ec), alloc); + wg1_.reset(); + } +}; + +struct stream::run_read_op +{ + template< + class ReadHandler, + class MutableBufferSequence> + void + operator()( + ReadHandler&& h, + boost::shared_ptr const& in, + MutableBufferSequence const& buffers) + { + // If you get an error on the following line it means + // that your handler does not meet the documented type + // requirements for the handler. + + static_assert( + beast::detail::is_invocable::value, + "ReadHandler type requirements not met"); + + initiate_read( + in, + std::unique_ptr{ + new read_op< + typename std::decay::type, + MutableBufferSequence>( + std::move(h), + in, + buffers)}, + buffer_bytes(buffers)); + } +}; + +struct stream::run_write_op +{ + template< + class WriteHandler, + class ConstBufferSequence> + void + operator()( + WriteHandler&& h, + boost::shared_ptr in_, + boost::weak_ptr out_, + ConstBufferSequence const& buffers) + { + // If you get an error on the following line it means + // that your handler does not meet the documented type + // requirements for the handler. + + static_assert( + beast::detail::is_invocable::value, + "WriteHandler type requirements not met"); + + ++in_->nwrite; + auto const upcall = [&](error_code ec, std::size_t n) + { + net::post( + in_->ioc.get_executor(), + beast::bind_front_handler(std::move(h), ec, n)); + }; + + // test failure + error_code ec; + std::size_t n = 0; + if(in_->fc && in_->fc->fail(ec)) + return upcall(ec, n); + + // A request to write 0 bytes to a stream is a no-op. + if(buffer_bytes(buffers) == 0) + return upcall(ec, n); + + // connection closed + auto out = out_.lock(); + if(! out) + return upcall(net::error::connection_reset, n); + + // copy buffers + n = std::min( + buffer_bytes(buffers), in_->write_max); + { + std::lock_guard lock(out->m); + n = net::buffer_copy(out->b.prepare(n), buffers); + out->b.commit(n); + out->nwrite_bytes += n; + out->notify_read(); + } + BOOST_ASSERT(! ec); + upcall(ec, n); + } +}; + +//------------------------------------------------------------------------------ + +template +std::size_t +stream:: +read_some(MutableBufferSequence const& buffers) +{ + static_assert(net::is_mutable_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence type requirements not met"); + error_code ec; + auto const n = read_some(buffers, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return n; +} + +template +std::size_t +stream:: +read_some(MutableBufferSequence const& buffers, + error_code& ec) +{ + static_assert(net::is_mutable_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence type requirements not met"); + + ++in_->nread; + + // test failure + if(in_->fc && in_->fc->fail(ec)) + return 0; + + // A request to read 0 bytes from a stream is a no-op. + if(buffer_bytes(buffers) == 0) + { + ec = {}; + return 0; + } + + std::unique_lock lock{in_->m}; + BOOST_ASSERT(! in_->op); + in_->cv.wait(lock, + [&]() + { + return + in_->b.size() > 0 || + in_->code != status::ok; + }); + + // deliver bytes before eof + if(in_->b.size() > 0) + { + auto const n = net::buffer_copy( + buffers, in_->b.data(), in_->read_max); + in_->b.consume(n); + in_->nread_bytes += n; + return n; + } + + // deliver error + BOOST_ASSERT(in_->code != status::ok); + ec = net::error::eof; + return 0; +} + +template +BOOST_BEAST_ASYNC_RESULT2(ReadHandler) +stream:: +async_read_some( + MutableBufferSequence const& buffers, + ReadHandler&& handler) +{ + static_assert(net::is_mutable_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence type requirements not met"); + + return net::async_initiate< + ReadHandler, + void(error_code, std::size_t)>( + run_read_op{}, + handler, + in_, + buffers); +} + +template +std::size_t +stream:: +write_some(ConstBufferSequence const& buffers) +{ + static_assert(net::is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence type requirements not met"); + error_code ec; + auto const bytes_transferred = + write_some(buffers, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return bytes_transferred; +} + +template +std::size_t +stream:: +write_some( + ConstBufferSequence const& buffers, error_code& ec) +{ + static_assert(net::is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence type requirements not met"); + + ++in_->nwrite; + + // test failure + if(in_->fc && in_->fc->fail(ec)) + return 0; + + // A request to write 0 bytes to a stream is a no-op. + if(buffer_bytes(buffers) == 0) + { + ec = {}; + return 0; + } + + // connection closed + auto out = out_.lock(); + if(! out) + { + ec = net::error::connection_reset; + return 0; + } + + // copy buffers + auto n = std::min( + buffer_bytes(buffers), in_->write_max); + { + std::lock_guard lock(out->m); + n = net::buffer_copy(out->b.prepare(n), buffers); + out->b.commit(n); + out->nwrite_bytes += n; + out->notify_read(); + } + return n; +} + +template +BOOST_BEAST_ASYNC_RESULT2(WriteHandler) +stream:: +async_write_some( + ConstBufferSequence const& buffers, + WriteHandler&& handler) +{ + static_assert(net::is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence type requirements not met"); + + return net::async_initiate< + WriteHandler, + void(error_code, std::size_t)>( + run_write_op{}, + handler, + in_, + out_, + buffers); +} + +//------------------------------------------------------------------------------ + +template +void +async_teardown( + role_type, + stream& s, + TeardownHandler&& handler) +{ + error_code ec; + if( s.in_->fc && + s.in_->fc->fail(ec)) + return net::post( + s.get_executor(), + beast::bind_front_handler( + std::move(handler), ec)); + s.close(); + if( s.in_->fc && + s.in_->fc->fail(ec)) + ec = net::error::eof; + else + ec = {}; + + net::post( + s.get_executor(), + beast::bind_front_handler( + std::move(handler), ec)); +} + +//------------------------------------------------------------------------------ + +template +stream +connect(stream& to, Arg1&& arg1, ArgN&&... argn) +{ + stream from{ + std::forward(arg1), + std::forward(argn)...}; + from.connect(to); + return from; +} + +} // test +} // beast +} // boost + +#endif diff --git a/test/utils/impl/stream.ipp b/test/utils/impl/stream.ipp new file mode 100644 index 00000000..1570ccaa --- /dev/null +++ b/test/utils/impl/stream.ipp @@ -0,0 +1,377 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_TEST_IMPL_STREAM_IPP +#define BOOST_BEAST_TEST_IMPL_STREAM_IPP + +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace test { + +//------------------------------------------------------------------------------ + +stream:: +service:: +service(net::execution_context& ctx) + : beast::detail::service_base(ctx) + , sp_(boost::make_shared()) +{ +} + +void +stream:: +service:: +shutdown() +{ + std::vector> v; + std::lock_guard g1(sp_->m_); + v.reserve(sp_->v_.size()); + for(auto p : sp_->v_) + { + std::lock_guard g2(p->m); + v.emplace_back(std::move(p->op)); + p->code = status::eof; + } +} + +auto +stream:: +service:: +make_impl( + net::io_context& ctx, + test::fail_count* fc) -> + boost::shared_ptr +{ + auto& svc = net::use_service(ctx); + auto sp = boost::make_shared(ctx, svc.sp_, fc); + std::lock_guard g(svc.sp_->m_); + svc.sp_->v_.push_back(sp.get()); + return sp; +} + +void +stream:: +service_impl:: +remove(state& impl) +{ + std::lock_guard g(m_); + *std::find( + v_.begin(), v_.end(), + &impl) = std::move(v_.back()); + v_.pop_back(); +} + +//------------------------------------------------------------------------------ + +void stream::initiate_read( + boost::shared_ptr const& in_, + std::unique_ptr&& op, + std::size_t buf_size) +{ + std::unique_lock lock(in_->m); + + ++in_->nread; + if(in_->op != nullptr) + BOOST_THROW_EXCEPTION( + std::logic_error{"in_->op != nullptr"}); + + // test failure + error_code ec; + if(in_->fc && in_->fc->fail(ec)) + { + lock.unlock(); + (*op)(ec); + return; + } + + // A request to read 0 bytes from a stream is a no-op. + if(buf_size == 0 || buffer_bytes(in_->b.data()) > 0) + { + lock.unlock(); + (*op)(ec); + return; + } + + // deliver error + if(in_->code != status::ok) + { + lock.unlock(); + (*op)(net::error::eof); + return; + } + + // complete when bytes available or closed + in_->op = std::move(op); +} + +stream:: +state:: +state( + net::io_context& ioc_, + boost::weak_ptr wp_, + fail_count* fc_) + : ioc(ioc_) + , wp(std::move(wp_)) + , fc(fc_) +{ +} + +stream:: +state:: +~state() +{ + // cancel outstanding read + if(op != nullptr) + (*op)(net::error::operation_aborted); +} + +void +stream:: +state:: +remove() noexcept +{ + auto sp = wp.lock(); + + // If this goes off, it means the lifetime of a test::stream object + // extended beyond the lifetime of the associated execution context. + BOOST_ASSERT(sp); + + sp->remove(*this); +} + +void +stream:: +state:: +notify_read() +{ + if(op) + { + auto op_ = std::move(op); + op_->operator()(error_code{}); + } + else + { + cv.notify_all(); + } +} + +void +stream:: +state:: +cancel_read() +{ + std::unique_ptr p; + { + std::lock_guard lock(m); + code = status::eof; + p = std::move(op); + } + if(p != nullptr) + (*p)(net::error::operation_aborted); +} + +//------------------------------------------------------------------------------ + +stream:: +~stream() +{ + close(); + in_->remove(); +} + +stream:: +stream(stream&& other) +{ + auto in = service::make_impl( + other.in_->ioc, other.in_->fc); + in_ = std::move(other.in_); + out_ = std::move(other.out_); + other.in_ = in; +} + +stream& +stream:: +operator=(stream&& other) +{ + close(); + auto in = service::make_impl( + other.in_->ioc, other.in_->fc); + in_->remove(); + in_ = std::move(other.in_); + out_ = std::move(other.out_); + other.in_ = in; + return *this; +} + +//------------------------------------------------------------------------------ + +stream:: +stream(net::io_context& ioc) + : in_(service::make_impl(ioc, nullptr)) +{ +} + +stream:: +stream( + net::io_context& ioc, + fail_count& fc) + : in_(service::make_impl(ioc, &fc)) +{ +} + +stream:: +stream( + net::io_context& ioc, + string_view s) + : in_(service::make_impl(ioc, nullptr)) +{ + in_->b.commit(net::buffer_copy( + in_->b.prepare(s.size()), + net::buffer(s.data(), s.size()))); +} + +stream:: +stream( + net::io_context& ioc, + fail_count& fc, + string_view s) + : in_(service::make_impl(ioc, &fc)) +{ + in_->b.commit(net::buffer_copy( + in_->b.prepare(s.size()), + net::buffer(s.data(), s.size()))); +} + +void +stream:: +connect(stream& remote) +{ + BOOST_ASSERT(! out_.lock()); + BOOST_ASSERT(! remote.out_.lock()); + std::lock(in_->m, remote.in_->m); + std::lock_guard guard1{in_->m, std::adopt_lock}; + std::lock_guard guard2{remote.in_->m, std::adopt_lock}; + out_ = remote.in_; + remote.out_ = in_; + in_->code = status::ok; + remote.in_->code = status::ok; +} + +string_view +stream:: +str() const +{ + auto const bs = in_->b.data(); + if(buffer_bytes(bs) == 0) + return {}; + net::const_buffer const b = *net::buffer_sequence_begin(bs); + return {static_cast(b.data()), b.size()}; +} + +void +stream:: +append(string_view s) +{ + std::lock_guard lock{in_->m}; + in_->b.commit(net::buffer_copy( + in_->b.prepare(s.size()), + net::buffer(s.data(), s.size()))); +} + +void +stream:: +clear() +{ + std::lock_guard lock{in_->m}; + in_->b.consume(in_->b.size()); +} + +void +stream:: +close() +{ + in_->cancel_read(); + + // disconnect + { + auto out = out_.lock(); + out_.reset(); + + // notify peer + if(out) + { + std::lock_guard lock(out->m); + if(out->code == status::ok) + { + out->code = status::eof; + out->notify_read(); + } + } + } +} + +void +stream:: +close_remote() +{ + std::lock_guard lock{in_->m}; + if(in_->code == status::ok) + { + in_->code = status::eof; + in_->notify_read(); + } +} + +void +teardown( + role_type, + stream& s, + boost::system::error_code& ec) +{ + if( s.in_->fc && + s.in_->fc->fail(ec)) + return; + + s.close(); + + if( s.in_->fc && + s.in_->fc->fail(ec)) + ec = net::error::eof; + else + ec = {}; +} + +//------------------------------------------------------------------------------ + +stream +connect(stream& to) +{ + stream from{to.get_executor().context()}; + from.connect(to); + return from; +} + +void +connect(stream& s1, stream& s2) +{ + s1.connect(s2); +} + +} // test +} // beast +} // boost + +#endif diff --git a/test/utils/role.hpp b/test/utils/role.hpp new file mode 100644 index 00000000..23014172 --- /dev/null +++ b/test/utils/role.hpp @@ -0,0 +1,50 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_ROLE_HPP +#define BOOST_BEAST_ROLE_HPP + +#include + +namespace boost { +namespace beast { + +/** The role of local or remote peer. + + Whether the endpoint is a client or server affects the + behavior of teardown. + The teardown behavior also depends on the type of the stream + being torn down. + + The default implementation of teardown for regular + TCP/IP sockets is as follows: + + @li In the client role, a TCP/IP shutdown is sent after + reading all remaining data on the connection. + + @li In the server role, a TCP/IP shutdown is sent before + reading all remaining data on the connection. + + When the next layer type is a `net::ssl::stream`, + the connection is closed by performing the SSL closing + handshake corresponding to the role type, client or server. +*/ +enum class role_type +{ + /// The stream is operating as a client. + client, + + /// The stream is operating as a server. + server +}; + +} // beast +} // boost + +#endif diff --git a/test/utils/service_base.hpp b/test/utils/service_base.hpp new file mode 100644 index 00000000..1efe1215 --- /dev/null +++ b/test/utils/service_base.hpp @@ -0,0 +1,38 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_DETAIL_SERVICE_BASE_HPP +#define BOOST_BEAST_DETAIL_SERVICE_BASE_HPP + +#include + +namespace boost { +namespace beast { +namespace detail { + +template +struct service_base : net::execution_context::service +{ + static net::execution_context::id const id; + + explicit + service_base(net::execution_context& ctx) + : net::execution_context::service(ctx) + { + } +}; + +template +net::execution_context::id const service_base::id; + +} // detail +} // beast +} // boost + +#endif diff --git a/test/utils/stream.hpp b/test/utils/stream.hpp new file mode 100644 index 00000000..6b725ba1 --- /dev/null +++ b/test/utils/stream.hpp @@ -0,0 +1,629 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_TEST_STREAM_HPP +#define BOOST_BEAST_TEST_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if ! BOOST_BEAST_DOXYGEN +namespace boost { +namespace asio { +namespace ssl { +template class stream; +} // ssl +} // asio +} // boost +#endif + +namespace boost { +namespace beast { +namespace test { + +/** A two-way socket useful for unit testing + + An instance of this class simulates a traditional socket, + while also providing features useful for unit testing. + Each endpoint maintains an independent buffer called + the input area. Writes from one endpoint append data + to the peer's pending input area. When an endpoint performs + a read and data is present in the input area, the data is + delivered to the blocking or asynchronous operation. Otherwise + the operation is blocked or deferred until data is made + available, or until the endpoints become disconnected. + + These streams may be used anywhere an algorithm accepts a + reference to a synchronous or asynchronous read or write + stream. It is possible to use a test stream in a call to + `net::read_until`, or in a call to + @ref boost::beast::http::async_write for example. + + As with Boost.Asio I/O objects, a @ref stream constructs + with a reference to the `net::io_context` to use for + handling asynchronous I/O. For asynchronous operations, the + stream follows the same rules as a traditional asio socket + with respect to how completion handlers for asynchronous + operations are performed. + + To facilitate testing, these streams support some additional + features: + + @li The input area, represented by a @ref beast::basic_flat_buffer, + may be directly accessed by the caller to inspect the contents + before or after the remote endpoint writes data. This allows + a unit test to verify that the received data matches. + + @li Data may be manually appended to the input area. This data + will delivered in the next call to + @ref stream::read_some or @ref stream::async_read_some. + This allows predefined test vectors to be set up for testing + read algorithms. + + @li The stream may be constructed with a fail count. The + stream will eventually fail with a predefined error after a + certain number of operations, where the number of operations + is controlled by the test. When a test loops over a range of + operation counts, it is possible to exercise every possible + point of failure in the algorithm being tested. When used + correctly the technique allows the tests to reach a high + percentage of code coverage. + + @par Thread Safety + @e Distinct @e objects: Safe.@n + @e Shared @e objects: Unsafe. + The application must also ensure that all asynchronous + operations are performed within the same implicit or explicit strand. + + @par Concepts + @li SyncReadStream + @li SyncWriteStream + @li AsyncReadStream + @li AsyncWriteStream +*/ +class stream +{ + struct state; + + boost::shared_ptr in_; + boost::weak_ptr out_; + + enum class status + { + ok, + eof, + }; + + class service; + struct service_impl; + + struct read_op_base + { + virtual ~read_op_base() = default; + virtual void operator()(error_code ec) = 0; + }; + + struct state + { + friend class stream; + + net::io_context& ioc; + boost::weak_ptr wp; + std::mutex m; + flat_buffer b; + std::condition_variable cv; + std::unique_ptr op; + status code = status::ok; + fail_count* fc = nullptr; + std::size_t nread = 0; + std::size_t nread_bytes = 0; + std::size_t nwrite = 0; + std::size_t nwrite_bytes = 0; + std::size_t read_max = + (std::numeric_limits::max)(); + std::size_t write_max = + (std::numeric_limits::max)(); + + BOOST_BEAST_DECL + state( + net::io_context& ioc_, + boost::weak_ptr wp_, + fail_count* fc_); + + + BOOST_BEAST_DECL + ~state(); + + BOOST_BEAST_DECL + void + remove() noexcept; + + BOOST_BEAST_DECL + void + notify_read(); + + BOOST_BEAST_DECL + void + cancel_read(); + }; + + template + class read_op; + + struct run_read_op; + struct run_write_op; + + BOOST_BEAST_DECL + static + void + initiate_read( + boost::shared_ptr const& in, + std::unique_ptr&& op, + std::size_t buf_size); + +#if ! BOOST_BEAST_DOXYGEN + // boost::asio::ssl::stream needs these + // DEPRECATED + template + friend class boost::asio::ssl::stream; + // DEPRECATED + using lowest_layer_type = stream; + // DEPRECATED + lowest_layer_type& + lowest_layer() noexcept + { + return *this; + } + // DEPRECATED + lowest_layer_type const& + lowest_layer() const noexcept + { + return *this; + } +#endif + +public: + using buffer_type = flat_buffer; + + /** Destructor + + If an asynchronous read operation is pending, it will + simply be discarded with no notification to the completion + handler. + + If a connection is established while the stream is destroyed, + the peer will see the error `net::error::connection_reset` + when performing any reads or writes. + */ + BOOST_BEAST_DECL + ~stream(); + + /** Move Constructor + + Moving the stream while asynchronous operations are pending + results in undefined behavior. + */ + BOOST_BEAST_DECL + stream(stream&& other); + + /** Move Assignment + + Moving the stream while asynchronous operations are pending + results in undefined behavior. + */ + BOOST_BEAST_DECL + stream& + operator=(stream&& other); + + /** Construct a stream + + The stream will be created in a disconnected state. + + @param ioc The `io_context` object that the stream will use to + dispatch handlers for any asynchronous operations. + */ + BOOST_BEAST_DECL + explicit + stream(net::io_context& ioc); + + /** Construct a stream + + The stream will be created in a disconnected state. + + @param ioc The `io_context` object that the stream will use to + dispatch handlers for any asynchronous operations. + + @param fc The @ref fail_count to associate with the stream. + Each I/O operation performed on the stream will increment the + fail count. When the fail count reaches its internal limit, + a simulated failure error will be raised. + */ + BOOST_BEAST_DECL + stream( + net::io_context& ioc, + fail_count& fc); + + /** Construct a stream + + The stream will be created in a disconnected state. + + @param ioc The `io_context` object that the stream will use to + dispatch handlers for any asynchronous operations. + + @param s A string which will be appended to the input area, not + including the null terminator. + */ + BOOST_BEAST_DECL + stream( + net::io_context& ioc, + string_view s); + + /** Construct a stream + + The stream will be created in a disconnected state. + + @param ioc The `io_context` object that the stream will use to + dispatch handlers for any asynchronous operations. + + @param fc The @ref fail_count to associate with the stream. + Each I/O operation performed on the stream will increment the + fail count. When the fail count reaches its internal limit, + a simulated failure error will be raised. + + @param s A string which will be appended to the input area, not + including the null terminator. + */ + BOOST_BEAST_DECL + stream( + net::io_context& ioc, + fail_count& fc, + string_view s); + + /// Establish a connection + BOOST_BEAST_DECL + void + connect(stream& remote); + + /// The type of the executor associated with the object. + using executor_type = + net::io_context::executor_type; + + /// Return the executor associated with the object. + executor_type + get_executor() noexcept + { + return in_->ioc.get_executor(); + }; + + /// Set the maximum number of bytes returned by read_some + void + read_size(std::size_t n) noexcept + { + in_->read_max = n; + } + + /// Set the maximum number of bytes returned by write_some + void + write_size(std::size_t n) noexcept + { + in_->write_max = n; + } + + /// Direct input buffer access + buffer_type& + buffer() noexcept + { + return in_->b; + } + + /// Returns a string view representing the pending input data + BOOST_BEAST_DECL + string_view + str() const; + + /// Appends a string to the pending input data + BOOST_BEAST_DECL + void + append(string_view s); + + /// Clear the pending input area + BOOST_BEAST_DECL + void + clear(); + + /// Return the number of reads + std::size_t + nread() const noexcept + { + return in_->nread; + } + + /// Return the number of bytes read + std::size_t + nread_bytes() const noexcept + { + return in_->nread_bytes; + } + + /// Return the number of writes + std::size_t + nwrite() const noexcept + { + return in_->nwrite; + } + + /// Return the number of bytes written + std::size_t + nwrite_bytes() const noexcept + { + return in_->nwrite_bytes; + } + + /** Close the stream. + + The other end of the connection will see + `error::eof` after reading all the remaining data. + */ + BOOST_BEAST_DECL + void + close(); + + /** Close the other end of the stream. + + This end of the connection will see + `error::eof` after reading all the remaining data. + */ + BOOST_BEAST_DECL + void + close_remote(); + + /** Read some data from the stream. + + This function is used to read data from the stream. The function call will + block until one or more bytes of data has been read successfully, or until + an error occurs. + + @param buffers The buffers into which the data will be read. + + @returns The number of bytes read. + + @throws boost::system::system_error Thrown on failure. + + @note The `read_some` operation may not read all of the requested number of + bytes. Consider using the function `net::read` if you need to ensure + that the requested amount of data is read before the blocking operation + completes. + */ + template + std::size_t + read_some(MutableBufferSequence const& buffers); + + /** Read some data from the stream. + + This function is used to read data from the stream. The function call will + block until one or more bytes of data has been read successfully, or until + an error occurs. + + @param buffers The buffers into which the data will be read. + + @param ec Set to indicate what error occurred, if any. + + @returns The number of bytes read. + + @note The `read_some` operation may not read all of the requested number of + bytes. Consider using the function `net::read` if you need to ensure + that the requested amount of data is read before the blocking operation + completes. + */ + template + std::size_t + read_some(MutableBufferSequence const& buffers, + error_code& ec); + + /** Start an asynchronous read. + + This function is used to asynchronously read one or more bytes of data from + the stream. The function call always returns immediately. + + @param buffers The buffers into which the data will be read. Although the + buffers object may be copied as necessary, ownership of the underlying + buffers is retained by the caller, which must guarantee that they remain + valid until the handler is called. + + @param handler The completion handler to invoke when the operation + completes. The implementation takes ownership of the handler by + performing a decay-copy. The equivalent function signature of + the handler must be: + @code + void handler( + error_code const& ec, // Result of operation. + std::size_t bytes_transferred // Number of bytes read. + ); + @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `net::post`. + + @note The `async_read_some` operation may not read all of the requested number of + bytes. Consider using the function `net::async_read` if you need + to ensure that the requested amount of data is read before the asynchronous + operation completes. + */ + template< + class MutableBufferSequence, + BOOST_BEAST_ASYNC_TPARAM2 ReadHandler> + BOOST_BEAST_ASYNC_RESULT2(ReadHandler) + async_read_some( + MutableBufferSequence const& buffers, + ReadHandler&& handler); + + /** Write some data to the stream. + + This function is used to write data on the stream. The function call will + block until one or more bytes of data has been written successfully, or + until an error occurs. + + @param buffers The data to be written. + + @returns The number of bytes written. + + @throws boost::system::system_error Thrown on failure. + + @note The `write_some` operation may not transmit all of the data to the + peer. Consider using the function `net::write` if you need to + ensure that all data is written before the blocking operation completes. + */ + template + std::size_t + write_some(ConstBufferSequence const& buffers); + + /** Write some data to the stream. + + This function is used to write data on the stream. The function call will + block until one or more bytes of data has been written successfully, or + until an error occurs. + + @param buffers The data to be written. + + @param ec Set to indicate what error occurred, if any. + + @returns The number of bytes written. + + @note The `write_some` operation may not transmit all of the data to the + peer. Consider using the function `net::write` if you need to + ensure that all data is written before the blocking operation completes. + */ + template + std::size_t + write_some( + ConstBufferSequence const& buffers, error_code& ec); + + /** Start an asynchronous write. + + This function is used to asynchronously write one or more bytes of data to + the stream. The function call always returns immediately. + + @param buffers The data to be written to the stream. Although the buffers + object may be copied as necessary, ownership of the underlying buffers is + retained by the caller, which must guarantee that they remain valid until + the handler is called. + + @param handler The completion handler to invoke when the operation + completes. The implementation takes ownership of the handler by + performing a decay-copy. The equivalent function signature of + the handler must be: + @code + void handler( + error_code const& ec, // Result of operation. + std::size_t bytes_transferred // Number of bytes written. + ); + @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `net::post`. + + @note The `async_write_some` operation may not transmit all of the data to + the peer. Consider using the function `net::async_write` if you need + to ensure that all data is written before the asynchronous operation completes. + */ + template< + class ConstBufferSequence, + BOOST_BEAST_ASYNC_TPARAM2 WriteHandler> + BOOST_BEAST_ASYNC_RESULT2(WriteHandler) + async_write_some( + ConstBufferSequence const& buffers, + WriteHandler&& handler); + +#if ! BOOST_BEAST_DOXYGEN + friend + BOOST_BEAST_DECL + void + teardown( + role_type, + stream& s, + boost::system::error_code& ec); + + template + friend + BOOST_BEAST_DECL + void + async_teardown( + role_type role, + stream& s, + TeardownHandler&& handler); +#endif +}; + +#if ! BOOST_BEAST_DOXYGEN +inline +void +beast_close_socket(stream& s) +{ + s.close(); +} +#endif + +#if BOOST_BEAST_DOXYGEN +/** Return a new stream connected to the given stream + + @param to The stream to connect to. + + @param args Optional arguments forwarded to the new stream's constructor. + + @return The new, connected stream. +*/ +template +stream +connect(stream& to, Args&&... args); + +#else +BOOST_BEAST_DECL +stream +connect(stream& to); + +BOOST_BEAST_DECL +void +connect(stream& s1, stream& s2); + +template +stream +connect(stream& to, Arg1&& arg1, ArgN&&... argn); +#endif + +} // test +} // beast +} // boost + +#include +#ifdef BOOST_BEAST_HEADER_ONLY +#include +#endif + +#endif