summaryrefslogtreecommitdiff
path: root/boost/beast/_experimental
diff options
context:
space:
mode:
Diffstat (limited to 'boost/beast/_experimental')
-rw-r--r--boost/beast/_experimental/http/icy_stream.hpp319
-rw-r--r--boost/beast/_experimental/http/impl/icy_stream.hpp334
-rw-r--r--boost/beast/_experimental/test/error.hpp40
-rw-r--r--boost/beast/_experimental/test/fail_count.hpp70
-rw-r--r--boost/beast/_experimental/test/handler.hpp188
-rw-r--r--boost/beast/_experimental/test/impl/error.hpp40
-rw-r--r--boost/beast/_experimental/test/impl/error.ipp65
-rw-r--r--boost/beast/_experimental/test/impl/fail_count.ipp58
-rw-r--r--boost/beast/_experimental/test/impl/stream.hpp453
-rw-r--r--boost/beast/_experimental/test/impl/stream.ipp375
-rw-r--r--boost/beast/_experimental/test/stream.hpp607
-rw-r--r--boost/beast/_experimental/test/tcp.hpp77
-rw-r--r--boost/beast/_experimental/unit_test/amount.hpp59
-rw-r--r--boost/beast/_experimental/unit_test/detail/const_container.hpp95
-rw-r--r--boost/beast/_experimental/unit_test/dstream.hpp131
-rw-r--r--boost/beast/_experimental/unit_test/global_suites.hpp55
-rw-r--r--boost/beast/_experimental/unit_test/main.ipp87
-rw-r--r--boost/beast/_experimental/unit_test/match.hpp177
-rw-r--r--boost/beast/_experimental/unit_test/recorder.hpp93
-rw-r--r--boost/beast/_experimental/unit_test/reporter.hpp292
-rw-r--r--boost/beast/_experimental/unit_test/results.hpp246
-rw-r--r--boost/beast/_experimental/unit_test/runner.hpp292
-rw-r--r--boost/beast/_experimental/unit_test/suite.hpp707
-rw-r--r--boost/beast/_experimental/unit_test/suite_info.hpp126
-rw-r--r--boost/beast/_experimental/unit_test/suite_list.hpp81
-rw-r--r--boost/beast/_experimental/unit_test/thread.hpp128
26 files changed, 5195 insertions, 0 deletions
diff --git a/boost/beast/_experimental/http/icy_stream.hpp b/boost/beast/_experimental/http/icy_stream.hpp
new file mode 100644
index 0000000000..5d075fffed
--- /dev/null
+++ b/boost/beast/_experimental/http/icy_stream.hpp
@@ -0,0 +1,319 @@
+//
+// 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_HTTP_ICY_STREAM_HPP
+#define BOOST_BEAST_HTTP_ICY_STREAM_HPP
+
+#include <boost/beast/core/detail/config.hpp>
+#include <boost/beast/core/error.hpp>
+#include <boost/asio/async_result.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/logic/tribool.hpp>
+#include <type_traits>
+
+namespace boost {
+namespace beast {
+namespace http {
+
+/** Stream wrapper to process Shoutcast HTTP responses
+
+ This wrapper replaces the word "ICY" in the first
+ HTTP response received on the connection, with "HTTP/1.1".
+ This allows the Beast parser to be used with Shoutcast
+ servers, which send a non-standard HTTP message as the
+ response.
+
+ For asynchronous operations, the application must ensure
+ that they are are all performed within the same implicit
+ or explicit strand.
+
+ @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 Example
+ To use the @ref icy_stream template with an @ref tcp_stream
+ you would write:
+ @code
+ http::icy_stream<tcp_stream> is(ioc);
+ @endcode
+
+ @tparam NextLayer The type representing the next layer, to which
+ data will be read and written during operations. For synchronous
+ operations, the type must support the <em>SyncStream</em> concept.
+ For asynchronous operations, the type must support the
+ <em>AsyncStream</em> concept.
+
+ @note A stream object must not be moved or destroyed while there
+ are pending asynchronous operations associated with it.
+
+ @par Concepts
+ <em>AsyncStream</em>, <em>SyncStream</em>
+*/
+template<class NextLayer>
+class icy_stream
+{
+ NextLayer stream_;
+ char buf_[8];
+ unsigned char n_ = 0;
+ bool detect_ = true;
+
+ struct ops;
+
+ static
+ net::const_buffer
+ version()
+ {
+ return {"HTTP/1.1", 8};
+ }
+
+public:
+ /// The type of the next layer.
+ using next_layer_type =
+ typename std::remove_reference<NextLayer>::type;
+
+ /// The type of the executor associated with the object.
+ using executor_type = typename next_layer_type::executor_type;
+
+ icy_stream(icy_stream&&) = default;
+ icy_stream(icy_stream const&) = default;
+ icy_stream& operator=(icy_stream&&) = default;
+ icy_stream& operator=(icy_stream const&) = default;
+
+ /** Destructor
+
+ The treatment of pending operations will be the same as that
+ of the next layer.
+ */
+ ~icy_stream() = default;
+
+ /** Constructor
+
+ Arguments, if any, are forwarded to the next layer's constructor.
+ */
+ template<class... Args>
+ explicit
+ icy_stream(Args&&... args);
+
+ //--------------------------------------------------------------------------
+
+ /** Get the executor associated with the object.
+
+ This function may be used to obtain the executor object that the
+ stream uses to dispatch handlers for asynchronous operations.
+
+ @return A copy of the executor that stream will use to dispatch handlers.
+ */
+ executor_type
+ get_executor() noexcept
+ {
+ return stream_.get_executor();
+ }
+
+ /** Get a reference to the next layer
+
+ This function returns a reference to the next layer
+ in a stack of stream layers.
+
+ @return A reference to the next layer in the stack of
+ stream layers.
+ */
+ next_layer_type&
+ next_layer()
+ {
+ return stream_;
+ }
+
+ /** Get a reference to the next layer
+
+ This function returns a reference to the next layer in a
+ stack of stream layers.
+
+ @return A reference to the next layer in the stack of
+ stream layers.
+ */
+ next_layer_type const&
+ next_layer() const
+ {
+ return stream_;
+ }
+
+ //--------------------------------------------------------------------------
+
+ /** 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 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<class MutableBufferSequence>
+ 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<class MutableBufferSequence>
+ 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(
+ const boost::system::error_code& error, // 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,
+ class 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 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<class ConstBufferSequence>
+ 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<class ConstBufferSequence>
+ 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& error, // 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,
+ class WriteHandler>
+ BOOST_BEAST_ASYNC_RESULT2(WriteHandler)
+ async_write_some(
+ ConstBufferSequence const& buffers,
+ WriteHandler&& handler);
+};
+
+} // http
+} // beast
+} // boost
+
+#include <boost/beast/_experimental/http/impl/icy_stream.hpp>
+
+#endif
diff --git a/boost/beast/_experimental/http/impl/icy_stream.hpp b/boost/beast/_experimental/http/impl/icy_stream.hpp
new file mode 100644
index 0000000000..f7329180ee
--- /dev/null
+++ b/boost/beast/_experimental/http/impl/icy_stream.hpp
@@ -0,0 +1,334 @@
+//
+// 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_CORE_IMPL_ICY_STREAM_HPP
+#define BOOST_BEAST_CORE_IMPL_ICY_STREAM_HPP
+
+#include <boost/beast/core/async_base.hpp>
+#include <boost/beast/core/buffer_traits.hpp>
+#include <boost/beast/core/error.hpp>
+#include <boost/beast/core/stream_traits.hpp>
+#include <boost/beast/core/detail/is_invocable.hpp>
+#include <boost/asio/coroutine.hpp>
+#include <boost/assert.hpp>
+#include <boost/throw_exception.hpp>
+#include <cstring>
+#include <memory>
+#include <utility>
+
+namespace boost {
+namespace beast {
+namespace http {
+
+namespace detail {
+
+template<class ConstBufferSequence>
+boost::tribool
+is_icy(ConstBufferSequence const& buffers)
+{
+ char buf[3];
+ auto const n = net::buffer_copy(
+ net::mutable_buffer(buf, 3),
+ buffers);
+ if(n >= 1 && buf[0] != 'I')
+ return false;
+ if(n >= 2 && buf[1] != 'C')
+ return false;
+ if(n >= 3 && buf[2] != 'Y')
+ return false;
+ if(n < 3)
+ return boost::indeterminate;
+ return true;
+}
+
+} // detail
+
+template<class NextLayer>
+struct icy_stream<NextLayer>::ops
+{
+
+template<class Buffers, class Handler>
+class read_op
+ : public beast::async_base<Handler,
+ beast::executor_type<icy_stream>>
+ , public net::coroutine
+{
+ icy_stream& s_;
+ Buffers b_;
+ std::size_t n_ = 0;
+ error_code ec_;
+ bool match_ = false;
+
+public:
+ template<class Handler_>
+ read_op(
+ Handler_&& h,
+ icy_stream& s,
+ Buffers const& b)
+ : async_base<Handler,
+ beast::executor_type<icy_stream>>(
+ std::forward<Handler_>(h), s.get_executor())
+ , s_(s)
+ , b_(b)
+ {
+ (*this)({}, 0, false);
+ }
+
+ void
+ operator()(
+ error_code ec,
+ std::size_t bytes_transferred,
+ bool cont = true)
+ {
+ BOOST_ASIO_CORO_REENTER(*this)
+ {
+ if(s_.detect_)
+ {
+ BOOST_ASSERT(s_.n_ == 0);
+ for(;;)
+ {
+ // Try to read the first three characters
+ BOOST_ASIO_CORO_YIELD
+ s_.next_layer().async_read_some(
+ net::mutable_buffer(
+ s_.buf_ + s_.n_, 3 - s_.n_),
+ std::move(*this));
+ s_.n_ += static_cast<char>(bytes_transferred);
+ if(ec)
+ goto upcall;
+ auto result = detail::is_icy(
+ net::const_buffer(s_.buf_, s_.n_));
+ if(boost::indeterminate(result))
+ continue;
+ if(result)
+ s_.n_ = static_cast<char>(net::buffer_copy(
+ net::buffer(s_.buf_, sizeof(s_.buf_)),
+ icy_stream::version()));
+ break;
+ }
+ s_.detect_ = false;
+ }
+ if(s_.n_ > 0)
+ {
+ bytes_transferred = net::buffer_copy(
+ b_, net::const_buffer(s_.buf_, s_.n_));
+ s_.n_ -= static_cast<char>(bytes_transferred);
+ std::memmove(
+ s_.buf_,
+ s_.buf_ + bytes_transferred,
+ sizeof(s_.buf_) - bytes_transferred);
+ }
+ else
+ {
+ BOOST_ASIO_CORO_YIELD
+ s_.next_layer().async_read_some(
+ b_, std::move(*this));
+ }
+ upcall:
+ if(! cont)
+ {
+ ec_ = ec;
+ n_ = bytes_transferred;
+ BOOST_ASIO_CORO_YIELD
+ s_.next_layer().async_read_some(
+ net::mutable_buffer{},
+ std::move(*this));
+ ec = ec_;
+ bytes_transferred = n_;
+ }
+ this->complete_now(ec, bytes_transferred);
+ }
+ }
+};
+
+struct run_read_op
+{
+ template<class ReadHandler, class Buffers>
+ void
+ operator()(
+ ReadHandler&& h,
+ icy_stream* s,
+ Buffers const& b)
+ {
+ // 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<ReadHandler,
+ void(error_code, std::size_t)>::value,
+ "ReadHandler type requirements not met");
+
+ read_op<
+ Buffers,
+ typename std::decay<ReadHandler>::type>(
+ std::forward<ReadHandler>(h), *s, b);
+ }
+};
+
+};
+
+//------------------------------------------------------------------------------
+
+template<class NextLayer>
+template<class... Args>
+icy_stream<NextLayer>::
+icy_stream(Args&&... args)
+ : stream_(std::forward<Args>(args)...)
+{
+ std::memset(buf_, 0, sizeof(buf_));
+}
+
+template<class NextLayer>
+template<class MutableBufferSequence>
+std::size_t
+icy_stream<NextLayer>::
+read_some(MutableBufferSequence const& buffers)
+{
+ static_assert(is_sync_read_stream<next_layer_type>::value,
+ "SyncReadStream type requirements not met");
+ static_assert(net::is_mutable_buffer_sequence<
+ MutableBufferSequence>::value,
+ "MutableBufferSequence type requirements not met");
+ error_code ec;
+ auto n = read_some(buffers, ec);
+ if(ec)
+ BOOST_THROW_EXCEPTION(system_error{ec});
+ return n;
+}
+
+template<class NextLayer>
+template<class MutableBufferSequence>
+std::size_t
+icy_stream<NextLayer>::
+read_some(MutableBufferSequence const& buffers, error_code& ec)
+{
+ static_assert(is_sync_read_stream<next_layer_type>::value,
+ "SyncReadStream type requirements not met");
+ static_assert(net::is_mutable_buffer_sequence<
+ MutableBufferSequence>::value,
+ "MutableBufferSequence type requirements not met");
+ std::size_t bytes_transferred;
+ if(detect_)
+ {
+ BOOST_ASSERT(n_ == 0);
+ for(;;)
+ {
+ // Try to read the first three characters
+ bytes_transferred = next_layer().read_some(
+ net::mutable_buffer(buf_ + n_, 3 - n_), ec);
+ n_ += static_cast<char>(bytes_transferred);
+ if(ec)
+ return 0;
+ auto result = detail::is_icy(
+ net::const_buffer(buf_, n_));
+ if(boost::indeterminate(result))
+ continue;
+ if(result)
+ n_ = static_cast<char>(net::buffer_copy(
+ net::buffer(buf_, sizeof(buf_)),
+ icy_stream::version()));
+ break;
+ }
+ detect_ = false;
+ }
+ if(n_ > 0)
+ {
+ bytes_transferred = net::buffer_copy(
+ buffers, net::const_buffer(buf_, n_));
+ n_ -= static_cast<char>(bytes_transferred);
+ std::memmove(
+ buf_,
+ buf_ + bytes_transferred,
+ sizeof(buf_) - bytes_transferred);
+ }
+ else
+ {
+ bytes_transferred =
+ next_layer().read_some(buffers, ec);
+ }
+ return bytes_transferred;
+}
+
+template<class NextLayer>
+template<
+ class MutableBufferSequence,
+ class ReadHandler>
+BOOST_BEAST_ASYNC_RESULT2(ReadHandler)
+icy_stream<NextLayer>::
+async_read_some(
+ MutableBufferSequence const& buffers,
+ ReadHandler&& handler)
+{
+ static_assert(is_async_read_stream<next_layer_type>::value,
+ "AsyncReadStream type requirements not met");
+ 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)>(
+ typename ops::run_read_op{},
+ handler,
+ this,
+ buffers);
+}
+
+template<class NextLayer>
+template<class MutableBufferSequence>
+std::size_t
+icy_stream<NextLayer>::
+write_some(MutableBufferSequence const& buffers)
+{
+ static_assert(is_sync_write_stream<next_layer_type>::value,
+ "SyncWriteStream type requirements not met");
+ static_assert(net::is_const_buffer_sequence<
+ MutableBufferSequence>::value,
+ "MutableBufferSequence type requirements not met");
+ return stream_.write_some(buffers);
+}
+
+template<class NextLayer>
+template<class MutableBufferSequence>
+std::size_t
+icy_stream<NextLayer>::
+write_some(MutableBufferSequence const& buffers, error_code& ec)
+{
+ static_assert(is_sync_write_stream<next_layer_type>::value,
+ "SyncWriteStream type requirements not met");
+ static_assert(net::is_const_buffer_sequence<
+ MutableBufferSequence>::value,
+ "MutableBufferSequence type requirements not met");
+ return stream_.write_some(buffers, ec);
+}
+
+template<class NextLayer>
+template<
+ class MutableBufferSequence,
+ class WriteHandler>
+BOOST_BEAST_ASYNC_RESULT2(WriteHandler)
+icy_stream<NextLayer>::
+async_write_some(
+ MutableBufferSequence const& buffers,
+ WriteHandler&& handler)
+{
+ static_assert(is_async_write_stream<next_layer_type>::value,
+ "AsyncWriteStream type requirements not met");
+ static_assert(net::is_const_buffer_sequence<
+ MutableBufferSequence>::value,
+ "MutableBufferSequence type requirements not met");
+ return stream_.async_write_some(
+ buffers, std::forward<WriteHandler>(handler));
+}
+
+} // http
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/test/error.hpp b/boost/beast/_experimental/test/error.hpp
new file mode 100644
index 0000000000..e857999aae
--- /dev/null
+++ b/boost/beast/_experimental/test/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 <boost/beast/core/detail/config.hpp>
+#include <boost/beast/core/error.hpp>
+
+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 <boost/beast/_experimental/test/impl/error.hpp>
+#ifdef BOOST_BEAST_HEADER_ONLY
+#include <boost/beast/_experimental/test/impl/error.ipp>
+#endif
+
+#endif
diff --git a/boost/beast/_experimental/test/fail_count.hpp b/boost/beast/_experimental/test/fail_count.hpp
new file mode 100644
index 0000000000..f56831919d
--- /dev/null
+++ b/boost/beast/_experimental/test/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 <boost/beast/core/detail/config.hpp>
+#include <boost/beast/_experimental/test/error.hpp>
+#include <cstdlib>
+
+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 <boost/beast/_experimental/test/impl/fail_count.ipp>
+#endif
+
+#endif
diff --git a/boost/beast/_experimental/test/handler.hpp b/boost/beast/_experimental/test/handler.hpp
new file mode 100644
index 0000000000..5998826563
--- /dev/null
+++ b/boost/beast/_experimental/test/handler.hpp
@@ -0,0 +1,188 @@
+//
+// 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_HANDLER_HPP
+#define BOOST_BEAST_TEST_HANDLER_HPP
+
+#include <boost/beast/_experimental/unit_test/suite.hpp>
+#include <boost/beast/core/error.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/core/exchange.hpp>
+#include <boost/optional.hpp>
+
+namespace boost {
+namespace beast {
+namespace test {
+
+/** A CompletionHandler used for testing.
+
+ This completion handler is used by tests to ensure correctness
+ of behavior. It is designed as a single type to reduce template
+ instantiations, with configurable settings through constructor
+ arguments. Typically this type will be used in type lists and
+ not instantiated directly; instances of this class are returned
+ by the helper functions listed below.
+
+ @see success_handler, @ref fail_handler, @ref any_handler
+*/
+class handler
+{
+ boost::optional<error_code> ec_;
+ bool pass_ = false;
+
+public:
+ handler() = default;
+
+ explicit
+ handler(error_code ec)
+ : ec_(ec)
+ {
+ }
+
+ explicit
+ handler(boost::none_t)
+ {
+ }
+
+ handler(handler&& other)
+ : ec_(other.ec_)
+ , pass_(boost::exchange(other.pass_, true))
+ {
+ }
+
+ ~handler()
+ {
+ BEAST_EXPECT(pass_);
+ }
+
+ template<class... Args>
+ void
+ operator()(error_code ec, Args&&...)
+ {
+ BEAST_EXPECT(! pass_); // can't call twice
+ BEAST_EXPECTS(! ec_ || ec == *ec_,
+ ec.message());
+ pass_ = true;
+ }
+
+ void
+ operator()()
+ {
+ BEAST_EXPECT(! pass_); // can't call twice
+ BEAST_EXPECT(! ec_);
+ pass_ = true;
+ }
+
+ template<class Arg0, class... Args,
+ class = typename std::enable_if<
+ ! std::is_convertible<Arg0, error_code>::value>::type>
+ void
+ operator()(Arg0&&, Args&&...)
+ {
+ BEAST_EXPECT(! pass_); // can't call twice
+ BEAST_EXPECT(! ec_);
+ pass_ = true;
+ }
+};
+
+/** Return a test CompletionHandler which requires success.
+
+ The returned handler can be invoked with any signature whose
+ first parameter is an `error_code`. The handler fails the test
+ if:
+
+ @li The handler is destroyed without being invoked, or
+
+ @li The handler is invoked with a non-successful error code.
+*/
+inline
+handler
+success_handler() noexcept
+{
+ return handler(error_code{});
+}
+
+/** Return a test CompletionHandler which requires invocation.
+
+ The returned handler can be invoked with any signature.
+ The handler fails the test if:
+
+ @li The handler is destroyed without being invoked.
+*/
+inline
+handler
+any_handler() noexcept
+{
+ return handler(boost::none);
+}
+
+/** Return a test CompletionHandler which requires a specific error code.
+
+ This handler can be invoked with any signature whose first
+ parameter is an `error_code`. The handler fails the test if:
+
+ @li The handler is destroyed without being invoked.
+
+ @li The handler is invoked with an error code different from
+ what is specified.
+
+ @param ec The error code to specify.
+*/
+inline
+handler
+fail_handler(error_code ec) noexcept
+{
+ return handler(ec);
+}
+
+/** Run an I/O context.
+
+ This function runs and dispatches handlers on the specified
+ I/O context, until one of the following conditions is true:
+
+ @li The I/O context runs out of work.
+
+ @param ioc The I/O context to run
+*/
+inline
+void
+run(net::io_context& ioc)
+{
+ ioc.run();
+ ioc.restart();
+}
+
+/** Run an I/O context for a certain amount of time.
+
+ This function runs and dispatches handlers on the specified
+ I/O context, until one of the following conditions is true:
+
+ @li The I/O context runs out of work.
+
+ @li No completions occur and the specified amount of time has elapsed.
+
+ @param ioc The I/O context to run
+
+ @param elapsed The maximum amount of time to run for.
+*/
+template<class Rep, class Period>
+void
+run_for(
+ net::io_context& ioc,
+ std::chrono::duration<Rep, Period> elapsed)
+{
+ ioc.run_for(elapsed);
+ ioc.restart();
+}
+
+} // test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/test/impl/error.hpp b/boost/beast/_experimental/test/impl/error.hpp
new file mode 100644
index 0000000000..e9c847954a
--- /dev/null
+++ b/boost/beast/_experimental/test/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 <boost/beast/core/error.hpp>
+#include <boost/beast/core/string.hpp>
+#include <type_traits>
+
+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/boost/beast/_experimental/test/impl/error.ipp b/boost/beast/_experimental/test/impl/error.ipp
new file mode 100644
index 0000000000..f444713761
--- /dev/null
+++ b/boost/beast/_experimental/test/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 <boost/beast/_experimental/test/error.hpp>
+
+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<error>(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<error>::type>(e), cat};
+}
+
+} // test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/test/impl/fail_count.ipp b/boost/beast/_experimental/test/impl/fail_count.ipp
new file mode 100644
index 0000000000..c0c2564220
--- /dev/null
+++ b/boost/beast/_experimental/test/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 <boost/beast/_experimental/test/fail_count.hpp>
+#include <boost/throw_exception.hpp>
+
+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/boost/beast/_experimental/test/impl/stream.hpp b/boost/beast/_experimental/test/impl/stream.hpp
new file mode 100644
index 0000000000..53bd4db7c8
--- /dev/null
+++ b/boost/beast/_experimental/test/impl/stream.hpp
@@ -0,0 +1,453 @@
+//
+// 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 <boost/beast/core/bind_handler.hpp>
+#include <boost/beast/core/buffer_traits.hpp>
+#include <boost/beast/core/buffers_prefix.hpp>
+#include <boost/beast/core/detail/service_base.hpp>
+#include <boost/beast/core/detail/type_traits.hpp>
+#include <mutex>
+#include <stdexcept>
+#include <vector>
+
+namespace boost {
+namespace beast {
+namespace test {
+
+//------------------------------------------------------------------------------
+
+struct stream::service_impl
+{
+ std::mutex m_;
+ std::vector<state*> v_;
+
+ BOOST_BEAST_DECL
+ void
+ remove(state& impl);
+};
+
+class stream::service
+ : public beast::detail::service_base<service>
+{
+ boost::shared_ptr<service_impl> 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<state>;
+};
+
+//------------------------------------------------------------------------------
+
+template<class Handler, class Buffers>
+class stream::read_op : public stream::read_op_base
+{
+ using ex1_type =
+ net::io_context::executor_type;
+ using ex2_type
+ = net::associated_executor_t<Handler, ex1_type>;
+
+ struct lambda
+ {
+ Handler h_;
+ boost::weak_ptr<state> wp_;
+ Buffers b_;
+ net::executor_work_guard<ex2_type> wg2_;
+
+ lambda(lambda&&) = default;
+ lambda(lambda const&) = default;
+
+ template<class Handler_>
+ lambda(
+ Handler_&& h,
+ boost::shared_ptr<state> const& s,
+ Buffers const& b)
+ : h_(std::forward<Handler_>(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<std::mutex> 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);
+ }
+ 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<ex1_type> wg1_;
+
+public:
+ template<class Handler_>
+ read_op(
+ Handler_&& h,
+ boost::shared_ptr<state> const& s,
+ Buffers const& b)
+ : fn_(std::forward<Handler_>(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<state> 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<ReadHandler,
+ void(error_code, std::size_t)>::value,
+ "ReadHandler type requirements not met");
+
+ initiate_read(
+ in,
+ std::unique_ptr<read_op_base>{
+ new read_op<
+ typename std::decay<ReadHandler>::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<state> in_,
+ boost::weak_ptr<state> 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<WriteHandler,
+ void(error_code, std::size_t)>::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<std::size_t>(
+ buffer_bytes(buffers), in_->write_max);
+ {
+ std::lock_guard<std::mutex> lock(out->m);
+ n = net::buffer_copy(out->b.prepare(n), buffers);
+ out->b.commit(n);
+ out->notify_read();
+ }
+ BOOST_ASSERT(! ec);
+ upcall(ec, n);
+ }
+};
+
+//------------------------------------------------------------------------------
+
+template<class MutableBufferSequence>
+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<class MutableBufferSequence>
+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<std::mutex> 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);
+ return n;
+ }
+
+ // deliver error
+ BOOST_ASSERT(in_->code != status::ok);
+ ec = net::error::eof;
+ return 0;
+}
+
+template<class MutableBufferSequence, class ReadHandler>
+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<class ConstBufferSequence>
+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<class ConstBufferSequence>
+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<std::size_t>(
+ buffer_bytes(buffers), in_->write_max);
+ {
+ std::lock_guard<std::mutex> lock(out->m);
+ n = net::buffer_copy(out->b.prepare(n), buffers);
+ out->b.commit(n);
+ out->notify_read();
+ }
+ return n;
+}
+
+template<class ConstBufferSequence, class WriteHandler>
+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<class TeardownHandler>
+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<class Arg1, class... ArgN>
+stream
+connect(stream& to, Arg1&& arg1, ArgN&&... argn)
+{
+ stream from{
+ std::forward<Arg1>(arg1),
+ std::forward<ArgN>(argn)...};
+ from.connect(to);
+ return from;
+}
+
+} // test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/test/impl/stream.ipp b/boost/beast/_experimental/test/impl/stream.ipp
new file mode 100644
index 0000000000..38b8a8b2a4
--- /dev/null
+++ b/boost/beast/_experimental/test/impl/stream.ipp
@@ -0,0 +1,375 @@
+//
+// 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 <boost/beast/_experimental/test/stream.hpp>
+#include <boost/beast/core/bind_handler.hpp>
+#include <boost/beast/core/buffer_traits.hpp>
+#include <boost/beast/core/buffers_prefix.hpp>
+#include <boost/make_shared.hpp>
+#include <stdexcept>
+#include <vector>
+
+namespace boost {
+namespace beast {
+namespace test {
+
+//------------------------------------------------------------------------------
+
+stream::
+service::
+service(net::execution_context& ctx)
+ : beast::detail::service_base<service>(ctx)
+ , sp_(boost::make_shared<service_impl>())
+{
+}
+
+void
+stream::
+service::
+shutdown()
+{
+ std::vector<std::unique_ptr<read_op_base>> v;
+ std::lock_guard<std::mutex> g1(sp_->m_);
+ v.reserve(sp_->v_.size());
+ for(auto p : sp_->v_)
+ {
+ std::lock_guard<std::mutex> 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<state>
+{
+ auto& svc = net::use_service<service>(ctx);
+ auto sp = boost::make_shared<state>(ctx, svc.sp_, fc);
+ std::lock_guard<std::mutex> g(svc.sp_->m_);
+ svc.sp_->v_.push_back(sp.get());
+ return sp;
+}
+
+void
+stream::
+service_impl::
+remove(state& impl)
+{
+ std::lock_guard<std::mutex> g(m_);
+ *std::find(
+ v_.begin(), v_.end(),
+ &impl) = std::move(v_.back());
+ v_.pop_back();
+}
+
+//------------------------------------------------------------------------------
+
+void stream::initiate_read(
+ boost::shared_ptr<state> const& in_,
+ std::unique_ptr<stream::read_op_base>&& op,
+ std::size_t buf_size)
+{
+ std::unique_lock<std::mutex> 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<service_impl> 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<read_op_base> p;
+ {
+ std::lock_guard<std::mutex> 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());
+ 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 {};
+ auto const b = beast::buffers_front(bs);
+ return {static_cast<char const*>(b.data()), b.size()};
+}
+
+void
+stream::
+append(string_view s)
+{
+ std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> lock(out->m);
+ if(out->code == status::ok)
+ {
+ out->code = status::eof;
+ out->notify_read();
+ }
+ }
+ }
+}
+
+void
+stream::
+close_remote()
+{
+ std::lock_guard<std::mutex> 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/boost/beast/_experimental/test/stream.hpp b/boost/beast/_experimental/test/stream.hpp
new file mode 100644
index 0000000000..181acec9aa
--- /dev/null
+++ b/boost/beast/_experimental/test/stream.hpp
@@ -0,0 +1,607 @@
+//
+// 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 <boost/beast/core/detail/config.hpp>
+#include <boost/beast/core/bind_handler.hpp>
+#include <boost/beast/core/flat_buffer.hpp>
+#include <boost/beast/core/role.hpp>
+#include <boost/beast/core/string.hpp>
+#include <boost/beast/_experimental/test/fail_count.hpp>
+#include <boost/asio/async_result.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/error.hpp>
+#include <boost/asio/executor_work_guard.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/post.hpp>
+#include <boost/assert.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <boost/throw_exception.hpp>
+#include <condition_variable>
+#include <limits>
+#include <memory>
+#include <mutex>
+#include <utility>
+
+#if ! BOOST_BEAST_DOXYGEN
+namespace boost {
+namespace asio {
+namespace ssl {
+template<typename> 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 <em>SyncReadStream</em>
+ @li <em>SyncWriteStream</em>
+ @li <em>AsyncReadStream</em>
+ @li <em>AsyncWriteStream</em>
+*/
+class stream
+{
+ struct state;
+
+ boost::shared_ptr<state> in_;
+ boost::weak_ptr<state> 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<service_impl> wp;
+ std::mutex m;
+ flat_buffer b;
+ std::condition_variable cv;
+ std::unique_ptr<read_op_base> op;
+ status code = status::ok;
+ fail_count* fc = nullptr;
+ std::size_t nread = 0;
+ std::size_t nwrite = 0;
+ std::size_t read_max =
+ (std::numeric_limits<std::size_t>::max)();
+ std::size_t write_max =
+ (std::numeric_limits<std::size_t>::max)();
+
+ BOOST_BEAST_DECL
+ state(
+ net::io_context& ioc_,
+ boost::weak_ptr<service_impl> 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 Handler, class Buffers>
+ class read_op;
+
+ struct run_read_op;
+ struct run_write_op;
+
+ BOOST_BEAST_DECL
+ static
+ void
+ initiate_read(
+ boost::shared_ptr<state> const& in,
+ std::unique_ptr<read_op_base>&& op,
+ std::size_t buf_size);
+
+#if ! BOOST_BEAST_DOXYGEN
+ // boost::asio::ssl::stream needs these
+ // DEPRECATED
+ template<class>
+ 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 writes
+ std::size_t
+ nwrite() const noexcept
+ {
+ return in_->nwrite;
+ }
+
+ /** 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<class MutableBufferSequence>
+ 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<class MutableBufferSequence>
+ 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, class 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<class ConstBufferSequence>
+ 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<class ConstBufferSequence>
+ 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, class 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<class TeardownHandler>
+ 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<class... Args>
+stream
+connect(stream& to, Args&&... args);
+
+#else
+BOOST_BEAST_DECL
+stream
+connect(stream& to);
+
+BOOST_BEAST_DECL
+void
+connect(stream& s1, stream& s2);
+
+template<class Arg1, class... ArgN>
+stream
+connect(stream& to, Arg1&& arg1, ArgN&&... argn);
+#endif
+
+} // test
+} // beast
+} // boost
+
+#include <boost/beast/_experimental/test/impl/stream.hpp>
+#ifdef BOOST_BEAST_HEADER_ONLY
+#include <boost/beast/_experimental/test/impl/stream.ipp>
+#endif
+
+#endif
diff --git a/boost/beast/_experimental/test/tcp.hpp b/boost/beast/_experimental/test/tcp.hpp
new file mode 100644
index 0000000000..d794b4ee42
--- /dev/null
+++ b/boost/beast/_experimental/test/tcp.hpp
@@ -0,0 +1,77 @@
+//
+// 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_TCP_HPP
+#define BOOST_BEAST_TEST_TCP_HPP
+
+#include <boost/beast/core/detail/config.hpp>
+#include <boost/beast/core/detail/get_io_context.hpp>
+#include <boost/beast/_experimental/unit_test/suite.hpp>
+#include <boost/beast/_experimental/test/handler.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <chrono>
+
+namespace boost {
+namespace beast {
+namespace test {
+
+/** Connect two TCP sockets together.
+*/
+template<class Executor>
+bool
+connect(
+ net::basic_stream_socket<net::ip::tcp, Executor>& s1,
+ net::basic_stream_socket<net::ip::tcp, Executor>& s2)
+
+{
+ auto ioc1 = beast::detail::get_io_context(s1);
+ auto ioc2 = beast::detail::get_io_context(s2);
+ if(! BEAST_EXPECT(ioc1 != nullptr))
+ return false;
+ if(! BEAST_EXPECT(ioc2 != nullptr))
+ return false;
+ if(! BEAST_EXPECT(ioc1 == ioc2))
+ return false;
+ auto& ioc = *ioc1;
+ try
+ {
+ net::basic_socket_acceptor<
+ net::ip::tcp, Executor> a(s1.get_executor());
+ auto ep = net::ip::tcp::endpoint(
+ net::ip::make_address_v4("127.0.0.1"), 0);
+ a.open(ep.protocol());
+ a.set_option(
+ net::socket_base::reuse_address(true));
+ a.bind(ep);
+ a.listen(0);
+ ep = a.local_endpoint();
+ a.async_accept(s2, test::success_handler());
+ s1.async_connect(ep, test::success_handler());
+ run(ioc);
+ if(! BEAST_EXPECT(
+ s1.remote_endpoint() == s2.local_endpoint()))
+ return false;
+ if(! BEAST_EXPECT(
+ s2.remote_endpoint() == s1.local_endpoint()))
+ return false;
+ }
+ catch(std::exception const& e)
+ {
+ beast::unit_test::suite::this_suite()->fail(
+ e.what(), __FILE__, __LINE__);
+ return false;
+ }
+ return true;
+}
+
+} // test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/unit_test/amount.hpp b/boost/beast/_experimental/unit_test/amount.hpp
new file mode 100644
index 0000000000..2d2969eacd
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/amount.hpp
@@ -0,0 +1,59 @@
+//
+// 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_UNIT_TEST_AMOUNT_HPP
+#define BOOST_BEAST_UNIT_TEST_AMOUNT_HPP
+
+#include <cstddef>
+#include <ostream>
+#include <string>
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+
+/** Utility for producing nicely composed output of amounts with units. */
+class amount
+{
+private:
+ std::size_t n_;
+ std::string const& what_;
+
+public:
+ amount(amount const&) = default;
+ amount& operator=(amount const&) = delete;
+
+ template<class = void>
+ amount(std::size_t n, std::string const& what);
+
+ friend
+ std::ostream&
+ operator<<(std::ostream& s, amount const& t);
+};
+
+template<class>
+amount::amount(std::size_t n, std::string const& what)
+ : n_(n)
+ , what_(what)
+{
+}
+
+inline
+std::ostream&
+operator<<(std::ostream& s, amount const& t)
+{
+ s << t.n_ << " " << t.what_ <<((t.n_ != 1) ? "s" : "");
+ return s;
+}
+
+} // unit_test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/unit_test/detail/const_container.hpp b/boost/beast/_experimental/unit_test/detail/const_container.hpp
new file mode 100644
index 0000000000..2f77912323
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/detail/const_container.hpp
@@ -0,0 +1,95 @@
+//
+// 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_UNIT_TEST_DETAIL_CONST_CONTAINER_HPP
+#define BOOST_BEAST_UNIT_TEST_DETAIL_CONST_CONTAINER_HPP
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+namespace detail {
+
+/** Adapter to constrain a container interface.
+ The interface allows for limited read only operations. Derived classes
+ provide additional behavior.
+*/
+template<class Container>
+class const_container
+{
+private:
+ using cont_type = Container;
+
+ cont_type m_cont;
+
+protected:
+ cont_type& cont()
+ {
+ return m_cont;
+ }
+
+ cont_type const& cont() const
+ {
+ return m_cont;
+ }
+
+public:
+ using value_type = typename cont_type::value_type;
+ using size_type = typename cont_type::size_type;
+ using difference_type = typename cont_type::difference_type;
+ using iterator = typename cont_type::const_iterator;
+ using const_iterator = typename cont_type::const_iterator;
+
+ /** Returns `true` if the container is empty. */
+ bool
+ empty() const
+ {
+ return m_cont.empty();
+ }
+
+ /** Returns the number of items in the container. */
+ size_type
+ size() const
+ {
+ return m_cont.size();
+ }
+
+ /** Returns forward iterators for traversal. */
+ /** @{ */
+ const_iterator
+ begin() const
+ {
+ return m_cont.cbegin();
+ }
+
+ const_iterator
+ cbegin() const
+ {
+ return m_cont.cbegin();
+ }
+
+ const_iterator
+ end() const
+ {
+ return m_cont.cend();
+ }
+
+ const_iterator
+ cend() const
+ {
+ return m_cont.cend();
+ }
+ /** @} */
+};
+
+} // detail
+} // unit_test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/unit_test/dstream.hpp b/boost/beast/_experimental/unit_test/dstream.hpp
new file mode 100644
index 0000000000..60066ac7bd
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/dstream.hpp
@@ -0,0 +1,131 @@
+//
+// 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_UNIT_TEST_DSTREAM_HPP
+#define BOOST_BEAST_UNIT_TEST_DSTREAM_HPP
+
+#include <boost/config.hpp>
+#include <ios>
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <streambuf>
+#include <string>
+
+#ifdef BOOST_WINDOWS
+#include <boost/winapi/basic_types.hpp>
+#include <boost/winapi/debugapi.hpp>
+#endif
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+
+#ifdef BOOST_WINDOWS
+
+namespace detail {
+
+template<class CharT, class Traits, class Allocator>
+class dstream_buf
+ : public std::basic_stringbuf<CharT, Traits, Allocator>
+{
+ using ostream = std::basic_ostream<CharT, Traits>;
+
+ ostream& os_;
+ bool dbg_;
+
+ template<class T>
+ void write(T const*) = delete;
+
+ void write(char const* s)
+ {
+ if(dbg_)
+ boost::winapi::OutputDebugStringA(s);
+ os_ << s;
+ }
+
+ void write(wchar_t const* s)
+ {
+ if(dbg_)
+ boost::winapi::OutputDebugStringW(s);
+ os_ << s;
+ }
+
+public:
+ explicit
+ dstream_buf(ostream& os)
+ : os_(os)
+ , dbg_(boost::winapi::IsDebuggerPresent() != 0)
+ {
+ }
+
+ ~dstream_buf()
+ {
+ sync();
+ }
+
+ int
+ sync() override
+ {
+ write(this->str().c_str());
+ this->str("");
+ return 0;
+ }
+};
+
+} // detail
+
+/** std::ostream with Visual Studio IDE redirection.
+
+ Instances of this stream wrap a specified `std::ostream`
+ (such as `std::cout` or `std::cerr`). If the IDE debugger
+ is attached when the stream is created, output will be
+ additionally copied to the Visual Studio Output window.
+*/
+template<
+ class CharT,
+ class Traits = std::char_traits<CharT>,
+ class Allocator = std::allocator<CharT>
+>
+class basic_dstream
+ : public std::basic_ostream<CharT, Traits>
+{
+ detail::dstream_buf<
+ CharT, Traits, Allocator> buf_;
+
+public:
+ /** Construct a stream.
+
+ @param os The output stream to wrap.
+ */
+ explicit
+ basic_dstream(std::ostream& os)
+ : std::basic_ostream<CharT, Traits>(&buf_)
+ , buf_(os)
+ {
+ if(os.flags() & std::ios::unitbuf)
+ std::unitbuf(*this);
+ }
+};
+
+using dstream = basic_dstream<char>;
+using dwstream = basic_dstream<wchar_t>;
+
+#else
+
+using dstream = std::ostream&;
+using dwstream = std::wostream&;
+
+#endif
+
+} // unit_test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/unit_test/global_suites.hpp b/boost/beast/_experimental/unit_test/global_suites.hpp
new file mode 100644
index 0000000000..2ad5dc7051
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/global_suites.hpp
@@ -0,0 +1,55 @@
+//
+// 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_UNIT_TEST_GLOBAL_SUITES_HPP
+#define BOOST_BEAST_UNIT_TEST_GLOBAL_SUITES_HPP
+
+#include <boost/beast/_experimental/unit_test/suite_list.hpp>
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+
+namespace detail {
+
+/// Holds test suites registered during static initialization.
+inline
+suite_list&
+global_suites()
+{
+ static suite_list s;
+ return s;
+}
+
+template<class Suite>
+struct insert_suite
+{
+ insert_suite(char const* name, char const* module,
+ char const* library, bool manual)
+ {
+ global_suites().insert<Suite>(
+ name, module, library, manual);
+ }
+};
+
+} // detail
+
+/// Holds test suites registered during static initialization.
+inline
+suite_list const&
+global_suites()
+{
+ return detail::global_suites();
+}
+
+} // unit_test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/unit_test/main.ipp b/boost/beast/_experimental/unit_test/main.ipp
new file mode 100644
index 0000000000..0a4d4c5555
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/main.ipp
@@ -0,0 +1,87 @@
+//
+// 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
+//
+
+#include <boost/beast/_experimental/unit_test/amount.hpp>
+#include <boost/beast/_experimental/unit_test/dstream.hpp>
+#include <boost/beast/_experimental/unit_test/global_suites.hpp>
+#include <boost/beast/_experimental/unit_test/match.hpp>
+#include <boost/beast/_experimental/unit_test/reporter.hpp>
+#include <boost/beast/_experimental/unit_test/suite.hpp>
+#include <boost/config.hpp>
+#include <cstdlib>
+#include <iostream>
+#include <vector>
+
+#ifdef BOOST_MSVC
+# ifndef WIN32_LEAN_AND_MEAN // VC_EXTRALEAN
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+# undef WIN32_LEAN_AND_MEAN
+# else
+# include <windows.h>
+# endif
+#endif
+
+// Simple main used to produce stand
+// alone executables that run unit tests.
+int main(int ac, char const* av[])
+{
+ using namespace std;
+ using namespace boost::beast::unit_test;
+
+ dstream log(std::cerr);
+ std::unitbuf(log);
+
+#ifdef BOOST_MSVC
+ {
+ int flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
+ flags |= _CRTDBG_LEAK_CHECK_DF;
+ _CrtSetDbgFlag(flags);
+ }
+#endif
+
+ if(ac == 2)
+ {
+ std::string const s{av[1]};
+ if(s == "-h" || s == "--help")
+ {
+ log <<
+ "Usage:\n"
+ " " << av[0] << ": { <suite-name>... }" <<
+ std::endl;
+ return EXIT_SUCCESS;
+ }
+ }
+
+ reporter r(log);
+ bool failed;
+ if(ac > 1)
+ {
+ std::vector<selector> v;
+ v.reserve(ac - 1);
+ for(int i = 1; i < ac; ++i)
+ v.emplace_back(selector::automatch, av[i]);
+ auto pred =
+ [&v](suite_info const& si) mutable
+ {
+ for(auto& p : v)
+ if(p(si))
+ return true;
+ return false;
+ };
+ failed = r.run_each_if(global_suites(), pred);
+ }
+ else
+ {
+ failed = r.run_each(global_suites());
+ }
+ if(failed)
+ return EXIT_FAILURE;
+ return EXIT_SUCCESS;
+}
diff --git a/boost/beast/_experimental/unit_test/match.hpp b/boost/beast/_experimental/unit_test/match.hpp
new file mode 100644
index 0000000000..0168af802e
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/match.hpp
@@ -0,0 +1,177 @@
+//
+// 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_UNIT_TEST_MATCH_HPP
+#define BOOST_BEAST_UNIT_TEST_MATCH_HPP
+
+#include <boost/beast/_experimental/unit_test/suite_info.hpp>
+#include <string>
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+
+// Predicate for implementing matches
+class selector
+{
+public:
+ enum mode_t
+ {
+ // Run all tests except manual ones
+ all,
+
+ // Run tests that match in any field
+ automatch,
+
+ // Match on suite
+ suite,
+
+ // Match on library
+ library,
+
+ // Match on module (used internally)
+ module,
+
+ // Match nothing (used internally)
+ none
+ };
+
+private:
+ mode_t mode_;
+ std::string pat_;
+ std::string library_;
+
+public:
+ template<class = void>
+ explicit
+ selector(mode_t mode, std::string const& pattern = "");
+
+ template<class = void>
+ bool
+ operator()(suite_info const& s);
+};
+
+//------------------------------------------------------------------------------
+
+template<class>
+selector::selector(mode_t mode, std::string const& pattern)
+ : mode_(mode)
+ , pat_(pattern)
+{
+ if(mode_ == automatch && pattern.empty())
+ mode_ = all;
+}
+
+template<class>
+bool
+selector::operator()(suite_info const& s)
+{
+ switch(mode_)
+ {
+ case automatch:
+ // suite or full name
+ if(s.name() == pat_ || s.full_name() == pat_)
+ {
+ mode_ = none;
+ return true;
+ }
+
+ // check module
+ if(pat_ == s.module())
+ {
+ mode_ = module;
+ library_ = s.library();
+ return ! s.manual();
+ }
+
+ // check library
+ if(pat_ == s.library())
+ {
+ mode_ = library;
+ return ! s.manual();
+ }
+
+ return false;
+
+ case suite:
+ return pat_ == s.name();
+
+ case module:
+ return pat_ == s.module() && ! s.manual();
+
+ case library:
+ return pat_ == s.library() && ! s.manual();
+
+ case none:
+ return false;
+
+ case all:
+ default:
+ // fall through
+ break;
+ };
+
+ return ! s.manual();
+}
+
+//------------------------------------------------------------------------------
+
+// Utility functions for producing predicates to select suites.
+
+/** Returns a predicate that implements a smart matching rule.
+ The predicate checks the suite, module, and library fields of the
+ suite_info in that order. When it finds a match, it changes modes
+ depending on what was found:
+
+ If a suite is matched first, then only the suite is selected. The
+ suite may be marked manual.
+
+ If a module is matched first, then only suites from that module
+ and library not marked manual are selected from then on.
+
+ If a library is matched first, then only suites from that library
+ not marked manual are selected from then on.
+
+*/
+inline
+selector
+match_auto(std::string const& name)
+{
+ return selector(selector::automatch, name);
+}
+
+/** Return a predicate that matches all suites not marked manual. */
+inline
+selector
+match_all()
+{
+ return selector(selector::all);
+}
+
+/** Returns a predicate that matches a specific suite. */
+inline
+selector
+match_suite(std::string const& name)
+{
+ return selector(selector::suite, name);
+}
+
+/** Returns a predicate that matches all suites in a library. */
+inline
+selector
+match_library(std::string const& name)
+{
+ return selector(selector::library, name);
+}
+
+} // unit_test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/unit_test/recorder.hpp b/boost/beast/_experimental/unit_test/recorder.hpp
new file mode 100644
index 0000000000..980fb78ab7
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/recorder.hpp
@@ -0,0 +1,93 @@
+//
+// 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_UNIT_TEST_RECORDER_HPP
+#define BOOST_BEAST_UNIT_TEST_RECORDER_HPP
+
+#include <boost/beast/_experimental/unit_test/results.hpp>
+#include <boost/beast/_experimental/unit_test/runner.hpp>
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+
+/** A test runner that stores the results. */
+class recorder : public runner
+{
+ results m_results;
+ suite_results m_suite;
+ case_results m_case;
+
+public:
+ recorder() = default;
+
+ /** Returns a report with the results of all completed suites. */
+ results const&
+ report() const
+ {
+ return m_results;
+ }
+
+private:
+ virtual
+ void
+ on_suite_begin(suite_info const& info) override
+ {
+ m_suite = suite_results(info.full_name());
+ }
+
+ virtual
+ void
+ on_suite_end() override
+ {
+ m_results.insert(std::move(m_suite));
+ }
+
+ virtual
+ void
+ on_case_begin(std::string const& name) override
+ {
+ m_case = case_results(name);
+ }
+
+ virtual
+ void
+ on_case_end() override
+ {
+ if(m_case.tests.size() > 0)
+ m_suite.insert(std::move(m_case));
+ }
+
+ virtual
+ void
+ on_pass() override
+ {
+ m_case.tests.pass();
+ }
+
+ virtual
+ void
+ on_fail(std::string const& reason) override
+ {
+ m_case.tests.fail(reason);
+ }
+
+ virtual
+ void
+ on_log(std::string const& s) override
+ {
+ m_case.log.insert(s);
+ }
+};
+
+} // unit_test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/unit_test/reporter.hpp b/boost/beast/_experimental/unit_test/reporter.hpp
new file mode 100644
index 0000000000..12a94f88dc
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/reporter.hpp
@@ -0,0 +1,292 @@
+//
+// 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_UNIT_TEST_REPORTER_HPP
+#define BOOST_BEAST_UNIT_TEST_REPORTER_HPP
+
+#include <boost/beast/_experimental/unit_test/amount.hpp>
+#include <boost/beast/_experimental/unit_test/recorder.hpp>
+#include <algorithm>
+#include <chrono>
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+
+namespace detail {
+
+/** A simple test runner that writes everything to a stream in real time.
+ The totals are output when the object is destroyed.
+*/
+template<class = void>
+class reporter : public runner
+{
+private:
+ using clock_type = std::chrono::steady_clock;
+
+ struct case_results
+ {
+ std::string name;
+ std::size_t total = 0;
+ std::size_t failed = 0;
+
+ explicit
+ case_results(std::string name_ = "")
+ : name(std::move(name_))
+ {
+ }
+ };
+
+ struct suite_results
+ {
+ std::string name;
+ std::size_t cases = 0;
+ std::size_t total = 0;
+ std::size_t failed = 0;
+ typename clock_type::time_point start = clock_type::now();
+
+ explicit
+ suite_results(std::string name_ = "")
+ : name(std::move(name_))
+ {
+ }
+
+ void
+ add(case_results const& r);
+ };
+
+ struct results
+ {
+ using run_time = std::pair<std::string,
+ typename clock_type::duration>;
+
+ enum
+ {
+ max_top = 10
+ };
+
+ std::size_t suites = 0;
+ std::size_t cases = 0;
+ std::size_t total = 0;
+ std::size_t failed = 0;
+ std::vector<run_time> top;
+ typename clock_type::time_point start = clock_type::now();
+
+ void
+ add(suite_results const& r);
+ };
+
+ std::ostream& os_;
+ results results_;
+ suite_results suite_results_;
+ case_results case_results_;
+
+public:
+ reporter(reporter const&) = delete;
+ reporter& operator=(reporter const&) = delete;
+
+ ~reporter();
+
+ explicit
+ reporter(std::ostream& os = std::cout);
+
+private:
+ static
+ std::string
+ fmtdur(typename clock_type::duration const& d);
+
+ virtual
+ void
+ on_suite_begin(suite_info const& info) override;
+
+ virtual
+ void
+ on_suite_end() override;
+
+ virtual
+ void
+ on_case_begin(std::string const& name) override;
+
+ virtual
+ void
+ on_case_end() override;
+
+ virtual
+ void
+ on_pass() override;
+
+ virtual
+ void
+ on_fail(std::string const& reason) override;
+
+ virtual
+ void
+ on_log(std::string const& s) override;
+};
+
+//------------------------------------------------------------------------------
+
+template<class _>
+void
+reporter<_>::
+suite_results::add(case_results const& r)
+{
+ ++cases;
+ total += r.total;
+ failed += r.failed;
+}
+
+template<class _>
+void
+reporter<_>::
+results::add(suite_results const& r)
+{
+ ++suites;
+ total += r.total;
+ cases += r.cases;
+ failed += r.failed;
+ auto const elapsed = clock_type::now() - r.start;
+ if(elapsed >= std::chrono::seconds{1})
+ {
+ auto const iter = std::lower_bound(top.begin(),
+ top.end(), elapsed,
+ [](run_time const& t1,
+ typename clock_type::duration const& t2)
+ {
+ return t1.second > t2;
+ });
+ if(iter != top.end())
+ {
+ top.emplace(iter, r.name, elapsed);
+ if(top.size() > max_top)
+ top.resize(max_top);
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+
+template<class _>
+reporter<_>::
+reporter(std::ostream& os)
+ : os_(os)
+{
+}
+
+template<class _>
+reporter<_>::~reporter()
+{
+ if(results_.top.size() > 0)
+ {
+ os_ << "Longest suite times:\n";
+ for(auto const& i : results_.top)
+ os_ << std::setw(8) <<
+ fmtdur(i.second) << " " << i.first << '\n';
+ }
+ auto const elapsed = clock_type::now() - results_.start;
+ os_ <<
+ fmtdur(elapsed) << ", " <<
+ amount{results_.suites, "suite"} << ", " <<
+ amount{results_.cases, "case"} << ", " <<
+ amount{results_.total, "test"} << " total, " <<
+ amount{results_.failed, "failure"} <<
+ std::endl;
+}
+
+template<class _>
+std::string
+reporter<_>::fmtdur(typename clock_type::duration const& d)
+{
+ using namespace std::chrono;
+ auto const ms = duration_cast<milliseconds>(d);
+ if(ms < seconds{1})
+ return std::to_string(ms.count()) + "ms";
+ std::stringstream ss;
+ ss << std::fixed << std::setprecision(1) <<
+ (ms.count()/1000.) << "s";
+ return ss.str();
+}
+
+template<class _>
+void
+reporter<_>::
+on_suite_begin(suite_info const& info)
+{
+ suite_results_ = suite_results{info.full_name()};
+}
+
+template<class _>
+void
+reporter<_>::on_suite_end()
+{
+ results_.add(suite_results_);
+}
+
+template<class _>
+void
+reporter<_>::
+on_case_begin(std::string const& name)
+{
+ case_results_ = case_results(name);
+ os_ << suite_results_.name <<
+ (case_results_.name.empty() ? "" :
+ (" " + case_results_.name)) << std::endl;
+}
+
+template<class _>
+void
+reporter<_>::
+on_case_end()
+{
+ suite_results_.add(case_results_);
+}
+
+template<class _>
+void
+reporter<_>::
+on_pass()
+{
+ ++case_results_.total;
+}
+
+template<class _>
+void
+reporter<_>::
+on_fail(std::string const& reason)
+{
+ ++case_results_.failed;
+ ++case_results_.total;
+ os_ <<
+ "#" << case_results_.total << " failed" <<
+ (reason.empty() ? "" : ": ") << reason << std::endl;
+}
+
+template<class _>
+void
+reporter<_>::
+on_log(std::string const& s)
+{
+ os_ << s;
+}
+
+} // detail
+
+using reporter = detail::reporter<>;
+
+} // unit_test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/unit_test/results.hpp b/boost/beast/_experimental/unit_test/results.hpp
new file mode 100644
index 0000000000..243d185f6b
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/results.hpp
@@ -0,0 +1,246 @@
+//
+// 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_UNIT_TEST_RESULTS_HPP
+#define BOOST_BEAST_UNIT_TEST_RESULTS_HPP
+
+#include <boost/beast/_experimental/unit_test/detail/const_container.hpp>
+
+#include <string>
+#include <vector>
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+
+/** Holds a set of test condition outcomes in a testcase. */
+class case_results
+{
+public:
+ /** Holds the result of evaluating one test condition. */
+ struct test
+ {
+ explicit test(bool pass_)
+ : pass(pass_)
+ {
+ }
+
+ test(bool pass_, std::string const& reason_)
+ : pass(pass_)
+ , reason(reason_)
+ {
+ }
+
+ bool pass;
+ std::string reason;
+ };
+
+private:
+ class tests_t
+ : public detail::const_container <std::vector <test>>
+ {
+ private:
+ std::size_t failed_;
+
+ public:
+ tests_t()
+ : failed_(0)
+ {
+ }
+
+ /** Returns the total number of test conditions. */
+ std::size_t
+ total() const
+ {
+ return cont().size();
+ }
+
+ /** Returns the number of failed test conditions. */
+ std::size_t
+ failed() const
+ {
+ return failed_;
+ }
+
+ /** Register a successful test condition. */
+ void
+ pass()
+ {
+ cont().emplace_back(true);
+ }
+
+ /** Register a failed test condition. */
+ void
+ fail(std::string const& reason = "")
+ {
+ ++failed_;
+ cont().emplace_back(false, reason);
+ }
+ };
+
+ class log_t
+ : public detail::const_container <std::vector <std::string>>
+ {
+ public:
+ /** Insert a string into the log. */
+ void
+ insert(std::string const& s)
+ {
+ cont().push_back(s);
+ }
+ };
+
+ std::string name_;
+
+public:
+ explicit case_results(std::string const& name = "")
+ : name_(name)
+ {
+ }
+
+ /** Returns the name of this testcase. */
+ std::string const&
+ name() const
+ {
+ return name_;
+ }
+
+ /** Memberspace for a container of test condition outcomes. */
+ tests_t tests;
+
+ /** Memberspace for a container of testcase log messages. */
+ log_t log;
+};
+
+//--------------------------------------------------------------------------
+
+/** Holds the set of testcase results in a suite. */
+class suite_results
+ : public detail::const_container <std::vector <case_results>>
+{
+private:
+ std::string name_;
+ std::size_t total_ = 0;
+ std::size_t failed_ = 0;
+
+public:
+ explicit suite_results(std::string const& name = "")
+ : name_(name)
+ {
+ }
+
+ /** Returns the name of this suite. */
+ std::string const&
+ name() const
+ {
+ return name_;
+ }
+
+ /** Returns the total number of test conditions. */
+ std::size_t
+ total() const
+ {
+ return total_;
+ }
+
+ /** Returns the number of failures. */
+ std::size_t
+ failed() const
+ {
+ return failed_;
+ }
+
+ /** Insert a set of testcase results. */
+ /** @{ */
+ void
+ insert(case_results&& r)
+ {
+ cont().emplace_back(std::move(r));
+ total_ += r.tests.total();
+ failed_ += r.tests.failed();
+ }
+
+ void
+ insert(case_results const& r)
+ {
+ cont().push_back(r);
+ total_ += r.tests.total();
+ failed_ += r.tests.failed();
+ }
+ /** @} */
+};
+
+//------------------------------------------------------------------------------
+
+// VFALCO TODO Make this a template class using scoped allocators
+/** Holds the results of running a set of testsuites. */
+class results
+ : public detail::const_container <std::vector <suite_results>>
+{
+private:
+ std::size_t m_cases;
+ std::size_t total_;
+ std::size_t failed_;
+
+public:
+ results()
+ : m_cases(0)
+ , total_(0)
+ , failed_(0)
+ {
+ }
+
+ /** Returns the total number of test cases. */
+ std::size_t
+ cases() const
+ {
+ return m_cases;
+ }
+
+ /** Returns the total number of test conditions. */
+ std::size_t
+ total() const
+ {
+ return total_;
+ }
+
+ /** Returns the number of failures. */
+ std::size_t
+ failed() const
+ {
+ return failed_;
+ }
+
+ /** Insert a set of suite results. */
+ /** @{ */
+ void
+ insert(suite_results&& r)
+ {
+ m_cases += r.size();
+ total_ += r.total();
+ failed_ += r.failed();
+ cont().emplace_back(std::move(r));
+ }
+
+ void
+ insert(suite_results const& r)
+ {
+ m_cases += r.size();
+ total_ += r.total();
+ failed_ += r.failed();
+ cont().push_back(r);
+ }
+ /** @} */
+};
+
+} // unit_test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/unit_test/runner.hpp b/boost/beast/_experimental/unit_test/runner.hpp
new file mode 100644
index 0000000000..d9a68fdda7
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/runner.hpp
@@ -0,0 +1,292 @@
+//
+// 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_UNIT_TEST_RUNNER_H_INCLUDED
+#define BOOST_BEAST_UNIT_TEST_RUNNER_H_INCLUDED
+
+#include <boost/beast/_experimental/unit_test/suite_info.hpp>
+#include <boost/assert.hpp>
+#include <mutex>
+#include <ostream>
+#include <string>
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+
+/** Unit test runner interface.
+
+ Derived classes can customize the reporting behavior. This interface is
+ injected into the unit_test class to receive the results of the tests.
+*/
+class runner
+{
+ std::string arg_;
+ bool default_ = false;
+ bool failed_ = false;
+ bool cond_ = false;
+ std::recursive_mutex mutex_;
+
+public:
+ runner() = default;
+ virtual ~runner() = default;
+ runner(runner const&) = delete;
+ runner& operator=(runner const&) = delete;
+
+ /** Set the argument string.
+
+ The argument string is available to suites and
+ allows for customization of the test. Each suite
+ defines its own syntax for the argument string.
+ The same argument is passed to all suites.
+ */
+ void
+ arg(std::string const& s)
+ {
+ arg_ = s;
+ }
+
+ /** Returns the argument string. */
+ std::string const&
+ arg() const
+ {
+ return arg_;
+ }
+
+ /** Run the specified suite.
+ @return `true` if any conditions failed.
+ */
+ template<class = void>
+ bool
+ run(suite_info const& s);
+
+ /** Run a sequence of suites.
+ The expression
+ `FwdIter::value_type`
+ must be convertible to `suite_info`.
+ @return `true` if any conditions failed.
+ */
+ template<class FwdIter>
+ bool
+ run(FwdIter first, FwdIter last);
+
+ /** Conditionally run a sequence of suites.
+ pred will be called as:
+ @code
+ bool pred(suite_info const&);
+ @endcode
+ @return `true` if any conditions failed.
+ */
+ template<class FwdIter, class Pred>
+ bool
+ run_if(FwdIter first, FwdIter last, Pred pred = Pred{});
+
+ /** Run all suites in a container.
+ @return `true` if any conditions failed.
+ */
+ template<class SequenceContainer>
+ bool
+ run_each(SequenceContainer const& c);
+
+ /** Conditionally run suites in a container.
+ pred will be called as:
+ @code
+ bool pred(suite_info const&);
+ @endcode
+ @return `true` if any conditions failed.
+ */
+ template<class SequenceContainer, class Pred>
+ bool
+ run_each_if(SequenceContainer const& c, Pred pred = Pred{});
+
+protected:
+ /// Called when a new suite starts.
+ virtual
+ void
+ on_suite_begin(suite_info const&)
+ {
+ }
+
+ /// Called when a suite ends.
+ virtual
+ void
+ on_suite_end()
+ {
+ }
+
+ /// Called when a new case starts.
+ virtual
+ void
+ on_case_begin(std::string const&)
+ {
+ }
+
+ /// Called when a new case ends.
+ virtual
+ void
+ on_case_end()
+ {
+ }
+
+ /// Called for each passing condition.
+ virtual
+ void
+ on_pass()
+ {
+ }
+
+ /// Called for each failing condition.
+ virtual
+ void
+ on_fail(std::string const&)
+ {
+ }
+
+ /// Called when a test logs output.
+ virtual
+ void
+ on_log(std::string const&)
+ {
+ }
+
+private:
+ friend class suite;
+
+ // Start a new testcase.
+ template<class = void>
+ void
+ testcase(std::string const& name);
+
+ template<class = void>
+ void
+ pass();
+
+ template<class = void>
+ void
+ fail(std::string const& reason);
+
+ template<class = void>
+ void
+ log(std::string const& s);
+};
+
+//------------------------------------------------------------------------------
+
+template<class>
+bool
+runner::run(suite_info const& s)
+{
+ // Enable 'default' testcase
+ default_ = true;
+ failed_ = false;
+ on_suite_begin(s);
+ s.run(*this);
+ // Forgot to call pass or fail.
+ BOOST_ASSERT(cond_);
+ on_case_end();
+ on_suite_end();
+ return failed_;
+}
+
+template<class FwdIter>
+bool
+runner::run(FwdIter first, FwdIter last)
+{
+ bool failed(false);
+ for(;first != last; ++first)
+ failed = run(*first) || failed;
+ return failed;
+}
+
+template<class FwdIter, class Pred>
+bool
+runner::run_if(FwdIter first, FwdIter last, Pred pred)
+{
+ bool failed(false);
+ for(;first != last; ++first)
+ if(pred(*first))
+ failed = run(*first) || failed;
+ return failed;
+}
+
+template<class SequenceContainer>
+bool
+runner::run_each(SequenceContainer const& c)
+{
+ bool failed(false);
+ for(auto const& s : c)
+ failed = run(s) || failed;
+ return failed;
+}
+
+template<class SequenceContainer, class Pred>
+bool
+runner::run_each_if(SequenceContainer const& c, Pred pred)
+{
+ bool failed(false);
+ for(auto const& s : c)
+ if(pred(s))
+ failed = run(s) || failed;
+ return failed;
+}
+
+template<class>
+void
+runner::testcase(std::string const& name)
+{
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
+ // Name may not be empty
+ BOOST_ASSERT(default_ || ! name.empty());
+ // Forgot to call pass or fail
+ BOOST_ASSERT(default_ || cond_);
+ if(! default_)
+ on_case_end();
+ default_ = false;
+ cond_ = false;
+ on_case_begin(name);
+}
+
+template<class>
+void
+runner::pass()
+{
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
+ if(default_)
+ testcase("");
+ on_pass();
+ cond_ = true;
+}
+
+template<class>
+void
+runner::fail(std::string const& reason)
+{
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
+ if(default_)
+ testcase("");
+ on_fail(reason);
+ failed_ = true;
+ cond_ = true;
+}
+
+template<class>
+void
+runner::log(std::string const& s)
+{
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
+ if(default_)
+ testcase("");
+ on_log(s);
+}
+
+} // unit_test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/unit_test/suite.hpp b/boost/beast/_experimental/unit_test/suite.hpp
new file mode 100644
index 0000000000..3cda8fe057
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/suite.hpp
@@ -0,0 +1,707 @@
+//
+// 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_UNIT_TEST_SUITE_HPP
+#define BOOST_BEAST_UNIT_TEST_SUITE_HPP
+
+#include <boost/beast/_experimental/unit_test/runner.hpp>
+#include <boost/throw_exception.hpp>
+#include <ostream>
+#include <sstream>
+#include <string>
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+
+namespace detail {
+
+template<class String>
+std::string
+make_reason(String const& reason,
+ char const* file, int line)
+{
+ std::string s(reason);
+ if(! s.empty())
+ s.append(": ");
+ char const* path = file + strlen(file);
+ while(path != file)
+ {
+ #ifdef _MSC_VER
+ if(path[-1] == '\\')
+ #else
+ if(path[-1] == '/')
+ #endif
+ break;
+ --path;
+ }
+ s.append(path);
+ s.append("(");
+ s.append(std::to_string(line));
+ s.append(")");
+ return s;
+}
+
+} // detail
+
+class thread;
+
+enum abort_t
+{
+ no_abort_on_fail,
+ abort_on_fail
+};
+
+/** A testsuite class.
+
+ Derived classes execute a series of testcases, where each testcase is
+ a series of pass/fail tests. To provide a unit test using this class,
+ derive from it and use the BOOST_BEAST_DEFINE_UNIT_TEST macro in a
+ translation unit.
+*/
+class suite
+{
+private:
+ bool abort_ = false;
+ bool aborted_ = false;
+ runner* runner_ = nullptr;
+
+ // This exception is thrown internally to stop the current suite
+ // in the event of a failure, if the option to stop is set.
+ struct abort_exception : public std::exception
+ {
+ char const*
+ what() const noexcept override
+ {
+ return "test suite aborted";
+ }
+ };
+
+ template<class CharT, class Traits, class Allocator>
+ class log_buf
+ : public std::basic_stringbuf<CharT, Traits, Allocator>
+ {
+ suite& suite_;
+
+ public:
+ explicit
+ log_buf(suite& self)
+ : suite_(self)
+ {
+ }
+
+ ~log_buf()
+ {
+ sync();
+ }
+
+ int
+ sync() override
+ {
+ auto const& s = this->str();
+ if(s.size() > 0)
+ suite_.runner_->log(s);
+ this->str("");
+ return 0;
+ }
+ };
+
+ template<
+ class CharT,
+ class Traits = std::char_traits<CharT>,
+ class Allocator = std::allocator<CharT>
+ >
+ class log_os : public std::basic_ostream<CharT, Traits>
+ {
+ log_buf<CharT, Traits, Allocator> buf_;
+
+ public:
+ explicit
+ log_os(suite& self)
+ : std::basic_ostream<CharT, Traits>(&buf_)
+ , buf_(self)
+ {
+ }
+ };
+
+ class scoped_testcase;
+
+ class testcase_t
+ {
+ suite& suite_;
+ std::stringstream ss_;
+
+ public:
+ explicit
+ testcase_t(suite& self)
+ : suite_(self)
+ {
+ }
+
+ /** Open a new testcase.
+
+ A testcase is a series of evaluated test conditions. A test
+ suite may have multiple test cases. A test is associated with
+ the last opened testcase. When the test first runs, a default
+ unnamed case is opened. Tests with only one case may omit the
+ call to testcase.
+
+ @param abort Determines if suite continues running after a failure.
+ */
+ void
+ operator()(std::string const& name,
+ abort_t abort = no_abort_on_fail);
+
+ scoped_testcase
+ operator()(abort_t abort);
+
+ template<class T>
+ scoped_testcase
+ operator<<(T const& t);
+ };
+
+public:
+ /** Logging output stream.
+
+ Text sent to the log output stream will be forwarded to
+ the output stream associated with the runner.
+ */
+ log_os<char> log;
+
+ /** Memberspace for declaring test cases. */
+ testcase_t testcase;
+
+ /** Returns the "current" running suite.
+ If no suite is running, nullptr is returned.
+ */
+ static
+ suite*
+ this_suite()
+ {
+ return *p_this_suite();
+ }
+
+ suite()
+ : log(*this)
+ , testcase(*this)
+ {
+ }
+
+ /** Invokes the test using the specified runner.
+
+ Data members are set up here instead of the constructor as a
+ convenience to writing the derived class to avoid repetition of
+ forwarded constructor arguments to the base.
+ Normally this is called by the framework for you.
+ */
+ template<class = void>
+ void
+ operator()(runner& r);
+
+ /** Record a successful test condition. */
+ template<class = void>
+ void
+ pass();
+
+ /** Record a failure.
+
+ @param reason Optional text added to the output on a failure.
+
+ @param file The source code file where the test failed.
+
+ @param line The source code line number where the test failed.
+ */
+ /** @{ */
+ template<class String>
+ void
+ fail(String const& reason, char const* file, int line);
+
+ template<class = void>
+ void
+ fail(std::string const& reason = "");
+ /** @} */
+
+ /** Evaluate a test condition.
+
+ This function provides improved logging by incorporating the
+ file name and line number into the reported output on failure,
+ as well as additional text specified by the caller.
+
+ @param shouldBeTrue The condition to test. The condition
+ is evaluated in a boolean context.
+
+ @param reason Optional added text to output on a failure.
+
+ @param file The source code file where the test failed.
+
+ @param line The source code line number where the test failed.
+
+ @return `true` if the test condition indicates success.
+ */
+ /** @{ */
+ template<class Condition>
+ bool
+ expect(Condition const& shouldBeTrue)
+ {
+ return expect(shouldBeTrue, "");
+ }
+
+ template<class Condition, class String>
+ bool
+ expect(Condition const& shouldBeTrue, String const& reason);
+
+ template<class Condition>
+ bool
+ expect(Condition const& shouldBeTrue,
+ char const* file, int line)
+ {
+ return expect(shouldBeTrue, "", file, line);
+ }
+
+ template<class Condition, class String>
+ bool
+ expect(Condition const& shouldBeTrue,
+ String const& reason, char const* file, int line);
+ /** @} */
+
+ //
+ // DEPRECATED
+ //
+ // Expect an exception from f()
+ template<class F, class String>
+ bool
+ except(F&& f, String const& reason);
+ template<class F>
+ bool
+ except(F&& f)
+ {
+ return except(f, "");
+ }
+ template<class E, class F, class String>
+ bool
+ except(F&& f, String const& reason);
+ template<class E, class F>
+ bool
+ except(F&& f)
+ {
+ return except<E>(f, "");
+ }
+ template<class F, class String>
+ bool
+ unexcept(F&& f, String const& reason);
+ template<class F>
+ bool
+ unexcept(F&& f)
+ {
+ return unexcept(f, "");
+ }
+
+ /** Return the argument associated with the runner. */
+ std::string const&
+ arg() const
+ {
+ return runner_->arg();
+ }
+
+ // DEPRECATED
+ // @return `true` if the test condition indicates success(a false value)
+ template<class Condition, class String>
+ bool
+ unexpected(Condition shouldBeFalse,
+ String const& reason);
+
+ template<class Condition>
+ bool
+ unexpected(Condition shouldBeFalse)
+ {
+ return unexpected(shouldBeFalse, "");
+ }
+
+private:
+ friend class thread;
+
+ static
+ suite**
+ p_this_suite()
+ {
+ static suite* pts = nullptr;
+ return &pts;
+ }
+
+ /** Runs the suite. */
+ virtual
+ void
+ run() = 0;
+
+ void
+ propagate_abort();
+
+ template<class = void>
+ void
+ run(runner& r);
+};
+
+//------------------------------------------------------------------------------
+
+// Helper for streaming testcase names
+class suite::scoped_testcase
+{
+private:
+ suite& suite_;
+ std::stringstream& ss_;
+
+public:
+ scoped_testcase& operator=(scoped_testcase const&) = delete;
+
+ ~scoped_testcase()
+ {
+ auto const& name = ss_.str();
+ if(! name.empty())
+ suite_.runner_->testcase(name);
+ }
+
+ scoped_testcase(suite& self, std::stringstream& ss)
+ : suite_(self)
+ , ss_(ss)
+ {
+ ss_.clear();
+ ss_.str({});
+ }
+
+ template<class T>
+ scoped_testcase(suite& self,
+ std::stringstream& ss, T const& t)
+ : suite_(self)
+ , ss_(ss)
+ {
+ ss_.clear();
+ ss_.str({});
+ ss_ << t;
+ }
+
+ template<class T>
+ scoped_testcase&
+ operator<<(T const& t)
+ {
+ ss_ << t;
+ return *this;
+ }
+};
+
+//------------------------------------------------------------------------------
+
+inline
+void
+suite::testcase_t::operator()(
+ std::string const& name, abort_t abort)
+{
+ suite_.abort_ = abort == abort_on_fail;
+ suite_.runner_->testcase(name);
+}
+
+inline
+suite::scoped_testcase
+suite::testcase_t::operator()(abort_t abort)
+{
+ suite_.abort_ = abort == abort_on_fail;
+ return { suite_, ss_ };
+}
+
+template<class T>
+inline
+suite::scoped_testcase
+suite::testcase_t::operator<<(T const& t)
+{
+ return { suite_, ss_, t };
+}
+
+//------------------------------------------------------------------------------
+
+template<class>
+void
+suite::
+operator()(runner& r)
+{
+ *p_this_suite() = this;
+ try
+ {
+ run(r);
+ *p_this_suite() = nullptr;
+ }
+ catch(...)
+ {
+ *p_this_suite() = nullptr;
+ throw;
+ }
+}
+
+template<class Condition, class String>
+bool
+suite::
+expect(
+ Condition const& shouldBeTrue, String const& reason)
+{
+ if(shouldBeTrue)
+ {
+ pass();
+ return true;
+ }
+ fail(reason);
+ return false;
+}
+
+template<class Condition, class String>
+bool
+suite::
+expect(Condition const& shouldBeTrue,
+ String const& reason, char const* file, int line)
+{
+ if(shouldBeTrue)
+ {
+ pass();
+ return true;
+ }
+ fail(detail::make_reason(reason, file, line));
+ return false;
+}
+
+// DEPRECATED
+
+template<class F, class String>
+bool
+suite::
+except(F&& f, String const& reason)
+{
+ try
+ {
+ f();
+ fail(reason);
+ return false;
+ }
+ catch(...)
+ {
+ pass();
+ }
+ return true;
+}
+
+template<class E, class F, class String>
+bool
+suite::
+except(F&& f, String const& reason)
+{
+ try
+ {
+ f();
+ fail(reason);
+ return false;
+ }
+ catch(E const&)
+ {
+ pass();
+ }
+ return true;
+}
+
+template<class F, class String>
+bool
+suite::
+unexcept(F&& f, String const& reason)
+{
+ try
+ {
+ f();
+ pass();
+ return true;
+ }
+ catch(...)
+ {
+ fail(reason);
+ }
+ return false;
+}
+
+template<class Condition, class String>
+bool
+suite::
+unexpected(
+ Condition shouldBeFalse, String const& reason)
+{
+ bool const b =
+ static_cast<bool>(shouldBeFalse);
+ if(! b)
+ pass();
+ else
+ fail(reason);
+ return ! b;
+}
+
+template<class>
+void
+suite::
+pass()
+{
+ propagate_abort();
+ runner_->pass();
+}
+
+// ::fail
+template<class>
+void
+suite::
+fail(std::string const& reason)
+{
+ propagate_abort();
+ runner_->fail(reason);
+ if(abort_)
+ {
+ aborted_ = true;
+ BOOST_THROW_EXCEPTION(abort_exception());
+ }
+}
+
+template<class String>
+void
+suite::
+fail(String const& reason, char const* file, int line)
+{
+ fail(detail::make_reason(reason, file, line));
+}
+
+inline
+void
+suite::
+propagate_abort()
+{
+ if(abort_ && aborted_)
+ BOOST_THROW_EXCEPTION(abort_exception());
+}
+
+template<class>
+void
+suite::
+run(runner& r)
+{
+ runner_ = &r;
+
+ try
+ {
+ run();
+ }
+ catch(abort_exception const&)
+ {
+ // ends the suite
+ }
+ catch(std::exception const& e)
+ {
+ runner_->fail("unhandled exception: " +
+ std::string(e.what()));
+ }
+ catch(...)
+ {
+ runner_->fail("unhandled exception");
+ }
+}
+
+#ifndef BEAST_PASS
+#define BEAST_PASS() ::boost::beast::unit_test::suite::this_suite()->pass()
+#endif
+
+#ifndef BEAST_FAIL
+#define BEAST_FAIL() ::boost::beast::unit_test::suite::this_suite()->fail("", __FILE__, __LINE__)
+#endif
+
+#ifndef BEAST_EXPECT
+/** Check a precondition.
+
+ If the condition is false, the file and line number are reported.
+*/
+#define BEAST_EXPECT(cond) ::boost::beast::unit_test::suite::this_suite()->expect(cond, __FILE__, __LINE__)
+#endif
+
+#ifndef BEAST_EXPECTS
+/** Check a precondition.
+
+ If the condition is false, the file and line number are reported.
+*/
+#define BEAST_EXPECTS(cond, reason) ((cond) ? \
+ (::boost::beast::unit_test::suite::this_suite()->pass(), true) : \
+ (::boost::beast::unit_test::suite::this_suite()->fail((reason), __FILE__, __LINE__), false))
+#endif
+
+} // unit_test
+} // beast
+} // boost
+
+//------------------------------------------------------------------------------
+
+// detail:
+// This inserts the suite with the given manual flag
+#define BEAST_DEFINE_TESTSUITE_INSERT(Library,Module,Class,manual) \
+ static ::boost::beast::unit_test::detail::insert_suite <Class##_test> \
+ Library ## Module ## Class ## _test_instance( \
+ #Class, #Module, #Library, manual)
+
+//------------------------------------------------------------------------------
+
+// Preprocessor directives for controlling unit test definitions.
+
+// If this is already defined, don't redefine it. This allows
+// programs to provide custom behavior for testsuite definitions
+//
+#ifndef BEAST_DEFINE_TESTSUITE
+
+/** Enables insertion of test suites into the global container.
+ The default is to insert all test suite definitions into the global
+ container. If BEAST_DEFINE_TESTSUITE is user defined, this macro
+ has no effect.
+*/
+#ifndef BEAST_NO_UNIT_TEST_INLINE
+#define BEAST_NO_UNIT_TEST_INLINE 0
+#endif
+
+/** Define a unit test suite.
+
+ Library Identifies the library.
+ Module Identifies the module.
+ Class The type representing the class being tested.
+
+ The declaration for the class implementing the test should be the same
+ as Class ## _test. For example, if Class is aged_ordered_container, the
+ test class must be declared as:
+
+ @code
+
+ struct aged_ordered_container_test : beast::unit_test::suite
+ {
+ //...
+ };
+
+ @endcode
+
+ The macro invocation must appear in the same namespace as the test class.
+*/
+
+#if BEAST_NO_UNIT_TEST_INLINE
+#define BEAST_DEFINE_TESTSUITE(Class,Module,Library)
+
+#else
+#include <boost/beast/_experimental/unit_test/global_suites.hpp>
+#define BEAST_DEFINE_TESTSUITE(Library,Module,Class) \
+ BEAST_DEFINE_TESTSUITE_INSERT(Library,Module,Class,false)
+#define BEAST_DEFINE_TESTSUITE_MANUAL(Library,Module,Class) \
+ BEAST_DEFINE_TESTSUITE_INSERT(Library,Module,Class,true)
+
+#endif
+
+#endif
+
+//------------------------------------------------------------------------------
+
+#endif
diff --git a/boost/beast/_experimental/unit_test/suite_info.hpp b/boost/beast/_experimental/unit_test/suite_info.hpp
new file mode 100644
index 0000000000..4e42183c92
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/suite_info.hpp
@@ -0,0 +1,126 @@
+//
+// 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_UNIT_TEST_SUITE_INFO_HPP
+#define BOOST_BEAST_UNIT_TEST_SUITE_INFO_HPP
+
+#include <cstring>
+#include <functional>
+#include <string>
+#include <utility>
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+
+class runner;
+
+/** Associates a unit test type with metadata. */
+class suite_info
+{
+ using run_type = std::function<void(runner&)>;
+
+ std::string name_;
+ std::string module_;
+ std::string library_;
+ bool manual_;
+ run_type run_;
+
+public:
+ suite_info(
+ std::string name,
+ std::string module,
+ std::string library,
+ bool manual,
+ run_type run)
+ : name_(std::move(name))
+ , module_(std::move(module))
+ , library_(std::move(library))
+ , manual_(manual)
+ , run_(std::move(run))
+ {
+ }
+
+ std::string const&
+ name() const
+ {
+ return name_;
+ }
+
+ std::string const&
+ module() const
+ {
+ return module_;
+ }
+
+ std::string const&
+ library() const
+ {
+ return library_;
+ }
+
+ /// Returns `true` if this suite only runs manually.
+ bool
+ manual() const
+ {
+ return manual_;
+ }
+
+ /// Return the canonical suite name as a string.
+ std::string
+ full_name() const
+ {
+ return library_ + "." + module_ + "." + name_;
+ }
+
+ /// Run a new instance of the associated test suite.
+ void
+ run(runner& r) const
+ {
+ run_(r);
+ }
+
+ friend
+ bool
+ operator<(suite_info const& lhs, suite_info const& rhs)
+ {
+ return
+ std::tie(lhs.library_, lhs.module_, lhs.name_) <
+ std::tie(rhs.library_, rhs.module_, rhs.name_);
+ }
+};
+
+//------------------------------------------------------------------------------
+
+/// Convenience for producing suite_info for a given test type.
+template<class Suite>
+suite_info
+make_suite_info(
+ std::string name,
+ std::string module,
+ std::string library,
+ bool manual)
+{
+ return suite_info(
+ std::move(name),
+ std::move(module),
+ std::move(library),
+ manual,
+ [](runner& r)
+ {
+ Suite{}(r);
+ }
+ );
+}
+
+} // unit_test
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/_experimental/unit_test/suite_list.hpp b/boost/beast/_experimental/unit_test/suite_list.hpp
new file mode 100644
index 0000000000..93ab16a431
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/suite_list.hpp
@@ -0,0 +1,81 @@
+//
+// 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_UNIT_TEST_SUITE_LIST_HPP
+#define BOOST_BEAST_UNIT_TEST_SUITE_LIST_HPP
+
+#include <boost/beast/_experimental/unit_test/suite_info.hpp>
+#include <boost/beast/_experimental/unit_test/detail/const_container.hpp>
+#include <boost/assert.hpp>
+#include <typeindex>
+#include <set>
+#include <unordered_set>
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+
+/// A container of test suites.
+class suite_list
+ : public detail::const_container <std::set <suite_info>>
+{
+private:
+#ifndef NDEBUG
+ std::unordered_set<std::string> names_;
+ std::unordered_set<std::type_index> classes_;
+#endif
+
+public:
+ /** Insert a suite into the set.
+
+ The suite must not already exist.
+ */
+ template<class Suite>
+ void
+ insert(
+ char const* name,
+ char const* module,
+ char const* library,
+ bool manual);
+};
+
+//------------------------------------------------------------------------------
+
+template<class Suite>
+void
+suite_list::insert(
+ char const* name,
+ char const* module,
+ char const* library,
+ bool manual)
+{
+#ifndef NDEBUG
+ {
+ std::string s;
+ s = std::string(library) + "." + module + "." + name;
+ auto const result(names_.insert(s));
+ BOOST_ASSERT(result.second); // Duplicate name
+ }
+
+ {
+ auto const result(classes_.insert(
+ std::type_index(typeid(Suite))));
+ BOOST_ASSERT(result.second); // Duplicate type
+ }
+#endif
+ cont().emplace(make_suite_info<Suite>(
+ name, module, library, manual));
+}
+
+} // unit_test
+} // beast
+} // boost
+
+#endif
+
diff --git a/boost/beast/_experimental/unit_test/thread.hpp b/boost/beast/_experimental/unit_test/thread.hpp
new file mode 100644
index 0000000000..ed06cb3731
--- /dev/null
+++ b/boost/beast/_experimental/unit_test/thread.hpp
@@ -0,0 +1,128 @@
+//
+// 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_UNIT_TEST_THREAD_HPP
+#define BOOST_BEAST_UNIT_TEST_THREAD_HPP
+
+#include <boost/beast/_experimental/unit_test/suite.hpp>
+#include <functional>
+#include <thread>
+#include <utility>
+
+namespace boost {
+namespace beast {
+namespace unit_test {
+
+/** Replacement for std::thread that handles exceptions in unit tests. */
+class thread
+{
+private:
+ suite* s_ = nullptr;
+ std::thread t_;
+
+public:
+ using id = std::thread::id;
+ using native_handle_type = std::thread::native_handle_type;
+
+ thread() = default;
+ thread(thread const&) = delete;
+ thread& operator=(thread const&) = delete;
+
+ thread(thread&& other)
+ : s_(other.s_)
+ , t_(std::move(other.t_))
+ {
+ }
+
+ thread& operator=(thread&& other)
+ {
+ s_ = other.s_;
+ t_ = std::move(other.t_);
+ return *this;
+ }
+
+ template<class F, class... Args>
+ explicit
+ thread(suite& s, F&& f, Args&&... args)
+ : s_(&s)
+ {
+ std::function<void(void)> b =
+ std::bind(std::forward<F>(f),
+ std::forward<Args>(args)...);
+ t_ = std::thread(&thread::run, this,
+ std::move(b));
+ }
+
+ bool
+ joinable() const
+ {
+ return t_.joinable();
+ }
+
+ std::thread::id
+ get_id() const
+ {
+ return t_.get_id();
+ }
+
+ static
+ unsigned
+ hardware_concurrency() noexcept
+ {
+ return std::thread::hardware_concurrency();
+ }
+
+ void
+ join()
+ {
+ t_.join();
+ s_->propagate_abort();
+ }
+
+ void
+ detach()
+ {
+ t_.detach();
+ }
+
+ void
+ swap(thread& other)
+ {
+ std::swap(s_, other.s_);
+ std::swap(t_, other.t_);
+ }
+
+private:
+ void
+ run(std::function <void(void)> f)
+ {
+ try
+ {
+ f();
+ }
+ catch(suite::abort_exception const&)
+ {
+ }
+ catch(std::exception const& e)
+ {
+ s_->fail("unhandled exception: " +
+ std::string(e.what()));
+ }
+ catch(...)
+ {
+ s_->fail("unhandled exception");
+ }
+ }
+};
+
+} // unit_test
+} // beast
+} // boost
+
+#endif