Move extras/ to test/

This commit is contained in:
Vinnie Falco committed 2017-07-31 19:02:46 -07:00
1 parent df9ec6c7f3
commit 030f5eef87
44 files changed
+25 -37

No files matched your search

@@ -0,0 +1,170 @@
//
// Copyright (c) 2016-2017 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)
//
#ifndef BOOST_BEAST_DOC_DEBUG_HPP
#define BOOST_BEAST_DOC_DEBUG_HPP
namespace beast {
#if BOOST_BEAST_DOXYGEN
/// doc type (documentation debug helper)
using doc_type = int;
/// doc enum (documentation debug helper)
enum doc_enum
{
/// One (documentation debug helper)
one,
/// Two (documentation debug helper)
two
};
/// doc enum class (documentation debug helper)
enum class doc_enum_class : unsigned
{
/// one (documentation debug helper)
one,
/// two (documentation debug helper)
two
};
/// doc func (documentation debug helper)
void doc_func();
/// doc class (documentation debug helper)
struct doc_class
{
/// doc class member func (documentation debug helper)
void func();
};
/// (documentation debug helper)
namespace nested {
/// doc type (documentation debug helper)
using nested_doc_type = int;
/// doc enum (documentation debug helper)
enum nested_doc_enum
{
/// One (documentation debug helper)
one,
/// Two (documentation debug helper)
two
};
/// doc enum class (documentation debug helper)
enum class nested_doc_enum_class : unsigned
{
/// one (documentation debug helper)
one,
/// two (documentation debug helper)
two
};
/// doc func (documentation debug helper)
void nested_doc_func();
/// doc class (documentation debug helper)
struct nested_doc_class
{
/// doc class member func (documentation debug helper)
void func();
};
} // nested
/** This is here to help troubleshoot doc/reference.xsl problems
Embedded references:
@li type @ref doc_type
@li enum @ref doc_enum
@li enum item @ref doc_enum::one
@li enum_class @ref doc_enum_class
@li enum_class item @ref doc_enum_class::one
@li func @ref doc_func
@li class @ref doc_class
@li class func @ref doc_class::func
@li nested type @ref nested::nested_doc_type
@li nested enum @ref nested::nested_doc_enum
@li nested enum item @ref nested::nested_doc_enum::one
@li nested enum_class @ref nested::nested_doc_enum_class
@li nested enum_class item @ref nested::nested_doc_enum_class::one
@li nested func @ref nested::nested_doc_func
@li nested class @ref nested::nested_doc_class
@li nested class func @ref nested::nested_doc_class::func
*/
void doc_debug();
namespace nested {
/** This is here to help troubleshoot doc/reference.xsl problems
Embedded references:
@li type @ref doc_type
@li enum @ref doc_enum
@li enum item @ref doc_enum::one
@li enum_class @ref doc_enum_class
@li enum_class item @ref doc_enum_class::one
@li func @ref doc_func
@li class @ref doc_class
@li class func @ref doc_class::func
@li nested type @ref nested_doc_type
@li nested enum @ref nested_doc_enum
@li nested enum item @ref nested_doc_enum::one
@li nested enum_class @ref nested_doc_enum_class
@li nested enum_class item @ref nested_doc_enum_class::one
@li nested func @ref nested_doc_func
@li nested class @ref nested_doc_class
@li nested class func @ref nested_doc_class::func
*/
void nested_doc_debug();
} // nested
#endif
} // beast
#endif
@@ -0,0 +1,174 @@
//
// Copyright (c) 2016-2017 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_COUNTER_HPP
#define BOOST_BEAST_TEST_FAIL_COUNTER_HPP
#include <boost/beast/core/error.hpp>
#include <boost/throw_exception.hpp>
namespace boost {
namespace beast {
namespace test {
enum class error
{
fail_error = 1
};
namespace detail {
class fail_error_category : public boost::system::error_category
{
public:
const char*
name() const noexcept override
{
return "test";
}
std::string
message(int ev) const override
{
switch(static_cast<error>(ev))
{
default:
case error::fail_error:
return "test error";
}
}
boost::system::error_condition
default_error_condition(int ev) const noexcept override
{
return boost::system::error_condition{ev, *this};
}
bool
equivalent(int ev,
boost::system::error_condition const& condition
) const noexcept override
{
return condition.value() == ev &&
&condition.category() == this;
}
bool
equivalent(error_code const& error, int ev) const noexcept override
{
return error.value() == ev &&
&error.category() == this;
}
};
inline
boost::system::error_category const&
get_error_category()
{
static fail_error_category const cat{};
return cat;
}
} // detail
inline
error_code
make_error_code(error ev)
{
return error_code{
static_cast<std::underlying_type<error>::type>(ev),
detail::get_error_category()};
}
/** An error code with an error set on default construction
Default constructed versions of this object will have
an error code set right away. This helps tests find code
which forgets to clear the error code on success.
*/
struct fail_error_code : error_code
{
fail_error_code()
: error_code(make_error_code(error::fail_error))
{
}
template<class Arg0, class... ArgN>
fail_error_code(Arg0&& arg0, ArgN&&... argn)
: error_code(arg0, std::forward<ArgN>(argn)...)
{
}
};
/** 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::fail_error.
*/
class fail_counter
{
std::size_t n_;
error_code ec_;
public:
fail_counter(fail_counter&&) = default;
/** Construct a counter.
@param The 0-based index of the operation to fail on or after.
*/
explicit
fail_counter(std::size_t n,
error_code ev = make_error_code(error::fail_error))
: n_(n)
, ec_(ev)
{
}
/// Throw an exception on the Nth failure
void
fail()
{
if(n_ > 0)
--n_;
if(! n_)
BOOST_THROW_EXCEPTION(system_error{ec_});
}
/// Set an error code on the Nth failure
bool
fail(error_code& ec)
{
if(n_ > 0)
--n_;
if(! n_)
{
ec = ec_;
return true;
}
ec.assign(0, ec.category());
return false;
}
};
} // test
} // beast
} // boost
namespace boost {
namespace system {
template<>
struct is_error_code_enum<beast::test::error>
{
static bool const value = true;
};
} // system
} // boost
#endif
@@ -0,0 +1,196 @@
//
// Copyright (c) 2016-2017 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_STREAM_HPP
#define BOOST_BEAST_TEST_FAIL_STREAM_HPP
#include <boost/beast/core/async_result.hpp>
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/detail/type_traits.hpp>
#include <boost/beast/websocket/teardown.hpp>
#include <boost/beast/test/fail_counter.hpp>
#include <boost/optional.hpp>
namespace boost {
namespace beast {
namespace test {
/** A stream wrapper that fails.
On the Nth operation, the stream will fail with the specified
error code, or the default error code of invalid_argument.
*/
template<class NextLayer>
class fail_stream
{
boost::optional<fail_counter> fc_;
fail_counter* pfc_;
NextLayer next_layer_;
public:
using next_layer_type =
typename std::remove_reference<NextLayer>::type;
using lowest_layer_type =
typename get_lowest_layer<next_layer_type>::type;
fail_stream(fail_stream&&) = delete;
fail_stream(fail_stream const&) = delete;
fail_stream& operator=(fail_stream&&) = delete;
fail_stream& operator=(fail_stream const&) = delete;
template<class... Args>
explicit
fail_stream(std::size_t n, Args&&... args)
: fc_(n)
, pfc_(&*fc_)
, next_layer_(std::forward<Args>(args)...)
{
}
template<class... Args>
explicit
fail_stream(fail_counter& fc, Args&&... args)
: pfc_(&fc)
, next_layer_(std::forward<Args>(args)...)
{
}
next_layer_type&
next_layer()
{
return next_layer_;
}
lowest_layer_type&
lowest_layer()
{
return next_layer_.lowest_layer();
}
lowest_layer_type const&
lowest_layer() const
{
return next_layer_.lowest_layer();
}
boost::asio::io_service&
get_io_service()
{
return next_layer_.get_io_service();
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers)
{
pfc_->fail();
return next_layer_.read_some(buffers);
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers, error_code& ec)
{
if(pfc_->fail(ec))
return 0;
return next_layer_.read_some(buffers, ec);
}
template<class MutableBufferSequence, class ReadHandler>
async_return_type<
ReadHandler, void(error_code, std::size_t)>
async_read_some(MutableBufferSequence const& buffers,
ReadHandler&& handler)
{
error_code ec;
if(pfc_->fail(ec))
{
async_completion<ReadHandler,
void(error_code, std::size_t)> init{handler};
next_layer_.get_io_service().post(
bind_handler(init.completion_handler, ec, 0));
return init.result.get();
}
return next_layer_.async_read_some(buffers,
std::forward<ReadHandler>(handler));
}
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers)
{
pfc_->fail();
return next_layer_.write_some(buffers);
}
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers, error_code& ec)
{
if(pfc_->fail(ec))
return 0;
return next_layer_.write_some(buffers, ec);
}
template<class ConstBufferSequence, class WriteHandler>
async_return_type<
WriteHandler, void(error_code, std::size_t)>
async_write_some(ConstBufferSequence const& buffers,
WriteHandler&& handler)
{
error_code ec;
if(pfc_->fail(ec))
{
async_completion<WriteHandler,
void(error_code, std::size_t)> init{handler};
next_layer_.get_io_service().post(
bind_handler(init.completion_handler, ec, 0));
return init.result.get();
}
return next_layer_.async_write_some(buffers,
std::forward<WriteHandler>(handler));
}
friend
void
teardown(websocket::teardown_tag,
fail_stream<NextLayer>& stream,
boost::system::error_code& ec)
{
if(stream.pfc_->fail(ec))
return;
beast::websocket_helpers::call_teardown(stream.next_layer(), ec);
}
template<class TeardownHandler>
friend
void
async_teardown(websocket::teardown_tag,
fail_stream<NextLayer>& stream,
TeardownHandler&& handler)
{
error_code ec;
if(stream.pfc_->fail(ec))
{
stream.get_io_service().post(
bind_handler(std::move(handler), ec));
return;
}
beast::websocket_helpers::call_async_teardown(
stream.next_layer(), std::forward<TeardownHandler>(handler));
}
};
} // test
} // beast
} // boost
#endif
@@ -0,0 +1,105 @@
//
// Copyright (c) 2016-2017 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_FUZZ_HPP
#define BOOST_BEAST_TEST_FUZZ_HPP
#include <boost/beast/core/static_string.hpp>
#include <boost/beast/core/string.hpp>
#include <random>
namespace boost {
namespace beast {
namespace test {
class fuzz_rand
{
std::mt19937 rng_;
public:
std::mt19937&
rng()
{
return rng_;
}
template<class Unsigned>
Unsigned
operator()(Unsigned n)
{
return static_cast<Unsigned>(
std::uniform_int_distribution<
Unsigned>{0, n-1}(rng_));
}
};
template<std::size_t N, class Rand, class F>
static
void
fuzz(
static_string<N> const& input,
std::size_t repeat,
std::size_t depth,
Rand& r,
F const& f)
{
static_string<N> mod{input};
for(auto i = repeat; i; --i)
{
switch(r(4))
{
case 0: // insert
if(mod.size() >= mod.max_size())
continue;
mod.insert(r(mod.size() + 1), 1,
static_cast<char>(r(256)));
break;
case 1: // erase
if(mod.size() == 0)
continue;
mod.erase(r(mod.size()), 1);
break;
case 2: // swap
{
if(mod.size() <= 1)
continue;
auto off = r(mod.size() - 1);
auto const temp = mod[off];
mod[off] = mod[off + 1];
mod[off + 1] = temp;
break;
}
case 3: // repeat
{
if(mod.empty())
continue;
auto n = (std::min)(
std::geometric_distribution<
std::size_t>{}(r.rng()),
mod.max_size() - mod.size());
if(n == 0)
continue;
auto off = r(mod.size());
mod.insert(off, n, mod[off + 1]);
break;
}
}
f(string_view{mod.data(), mod.size()});
if(depth > 0)
fuzz(mod, repeat, depth - 1, r, f);
}
}
} // test
} // beast
} // boost
#endif
@@ -0,0 +1,549 @@
//
// Copyright (c) 2016-2017 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_PIPE_STREAM_HPP
#define BOOST_BEAST_TEST_PIPE_STREAM_HPP
#include <boost/beast/core/async_result.hpp>
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/core/string.hpp>
#include <boost/beast/core/type_traits.hpp>
#include <boost/beast/websocket/teardown.hpp>
#include <boost/beast/test/fail_counter.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/assert.hpp>
#include <boost/throw_exception.hpp>
#include <condition_variable>
#include <limits>
#include <mutex>
#include <utility>
namespace boost {
namespace beast {
namespace test {
/** A bidirectional in-memory communication channel
An instance of this class provides a client and server
endpoint that are automatically connected to each other
similarly to a connected socket.
Test pipes are used to facilitate writing unit tests
where the behavior of the transport is tightly controlled
to help illuminate all code paths (for code coverage)
*/
class pipe
{
public:
using buffer_type = flat_buffer;
private:
struct read_op
{
virtual ~read_op() = default;
virtual void operator()() = 0;
};
struct state
{
std::mutex m;
buffer_type b;
std::condition_variable cv;
std::unique_ptr<read_op> op;
bool eof = false;
};
state s_[2];
public:
/** Represents an endpoint.
Each pipe has a client stream and a server stream.
*/
class stream
{
friend class pipe;
template<class Handler, class Buffers>
class read_op_impl;
state& in_;
state& out_;
boost::asio::io_service& ios_;
fail_counter* fc_ = nullptr;
std::size_t read_max_ =
(std::numeric_limits<std::size_t>::max)();
std::size_t write_max_ =
(std::numeric_limits<std::size_t>::max)();
stream(state& in, state& out,
boost::asio::io_service& ios)
: in_(in)
, out_(out)
, ios_(ios)
, buffer(in_.b)
{
}
public:
using buffer_type = pipe::buffer_type;
/// Direct access to the underlying buffer
buffer_type& buffer;
/// Counts the number of read calls
std::size_t nread = 0;
/// Counts the number of write calls
std::size_t nwrite = 0;
~stream() = default;
stream(stream&&) = default;
/// Set the fail counter on the object
#if 0
void
fail(fail_counter& fc)
{
fc_ = &fc;
}
#endif
/// Return the `io_service` associated with the object
boost::asio::io_service&
get_io_service()
{
return ios_;
}
/// Set the maximum number of bytes returned by read_some
void
read_size(std::size_t n)
{
read_max_ = n;
}
/// Set the maximum number of bytes returned by write_some
void
write_size(std::size_t n)
{
write_max_ = n;
}
/// Returns a string representing the pending input data
string_view
str() const
{
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
return {
buffer_cast<char const*>(*in_.b.data().begin()),
buffer_size(*in_.b.data().begin())};
}
/// Clear the buffer holding the input data
void
clear()
{
in_.b.consume((std::numeric_limits<
std::size_t>::max)());
}
/** Close the stream.
The other end of the pipe will see
`boost::asio::error::eof` on read.
*/
template<class = void>
void
close();
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers);
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers,
error_code& ec);
template<class MutableBufferSequence, class ReadHandler>
async_return_type<
ReadHandler, void(error_code, std::size_t)>
async_read_some(MutableBufferSequence const& buffers,
ReadHandler&& handler);
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers);
template<class ConstBufferSequence>
std::size_t
write_some(
ConstBufferSequence const& buffers, error_code&);
template<class ConstBufferSequence, class WriteHandler>
async_return_type<
WriteHandler, void(error_code, std::size_t)>
async_write_some(ConstBufferSequence const& buffers,
WriteHandler&& handler);
friend
void
teardown(websocket::teardown_tag,
stream& s, boost::system::error_code& ec);
template<class TeardownHandler>
friend
void
async_teardown(websocket::teardown_tag,
stream& s, TeardownHandler&& handler);
};
/** Constructor.
The client and server endpoints will use the same `io_service`.
*/
explicit
pipe(boost::asio::io_service& ios)
: client(s_[0], s_[1], ios)
, server(s_[1], s_[0], ios)
{
}
/// Represents the client endpoint
stream client;
/// Represents the server endpoint
stream server;
};
//------------------------------------------------------------------------------
inline
void
teardown(websocket::teardown_tag,
pipe::stream& s, boost::system::error_code& ec)
{
if(s.fc_)
{
if(s.fc_->fail(ec))
return;
}
else
{
s.close();
ec.assign(0, ec.category());
}
}
template<class TeardownHandler>
inline
void
async_teardown(websocket::teardown_tag,
pipe::stream& s, TeardownHandler&& handler)
{
error_code ec;
if(s.fc_ && s.fc_->fail(ec))
return s.get_io_service().post(
bind_handler(std::move(handler), ec));
s.close();
s.get_io_service().post(
bind_handler(std::move(handler), ec));
}
template<class Handler, class Buffers>
class pipe::stream::read_op_impl :
public pipe::read_op
{
stream& s_;
Buffers b_;
Handler h_;
public:
read_op_impl(stream& s,
Buffers const& b, Handler&& h)
: s_(s)
, b_(b)
, h_(std::move(h))
{
}
read_op_impl(stream& s,
Buffers const& b, Handler const& h)
: s_(s)
, b_(b)
, h_(h)
{
}
void
operator()() override;
};
//------------------------------------------------------------------------------
template<class Handler, class Buffers>
void
pipe::stream::
read_op_impl<Handler, Buffers>::operator()()
{
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
s_.ios_.post(
[&]()
{
BOOST_ASSERT(s_.in_.op);
std::unique_lock<std::mutex> lock{s_.in_.m};
if(s_.in_.b.size() > 0)
{
auto const bytes_transferred = buffer_copy(
b_, s_.in_.b.data(), s_.read_max_);
s_.in_.b.consume(bytes_transferred);
auto& s = s_;
Handler h{std::move(h_)};
s.in_.op.reset(nullptr);
lock.unlock();
++s.nread;
s.ios_.post(bind_handler(std::move(h),
error_code{}, bytes_transferred));
}
else
{
BOOST_ASSERT(s_.in_.eof);
auto& s = s_;
Handler h{std::move(h_)};
s.in_.op.reset(nullptr);
lock.unlock();
++s.nread;
s.ios_.post(bind_handler(std::move(h),
boost::asio::error::eof, 0));
}
});
}
//------------------------------------------------------------------------------
template<class>
void
pipe::stream::
close()
{
std::lock_guard<std::mutex> lock{out_.m};
if(! out_.eof)
{
out_.eof = true;
if(out_.op)
out_.op.get()->operator()();
else
out_.cv.notify_all();
}
}
template<class MutableBufferSequence>
std::size_t
pipe::stream::
read_some(MutableBufferSequence const& buffers)
{
static_assert(is_mutable_buffer_sequence<
MutableBufferSequence>::value,
"MutableBufferSequence 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
pipe::stream::
read_some(MutableBufferSequence const& buffers,
error_code& ec)
{
static_assert(is_mutable_buffer_sequence<
MutableBufferSequence>::value,
"MutableBufferSequence requirements not met");
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
BOOST_ASSERT(! in_.op);
BOOST_ASSERT(buffer_size(buffers) > 0);
if(fc_ && fc_->fail(ec))
return 0;
std::unique_lock<std::mutex> lock{in_.m};
in_.cv.wait(lock,
[&]()
{
return in_.b.size() > 0 || in_.eof;
});
std::size_t bytes_transferred;
if(in_.b.size() > 0)
{
ec.assign(0, ec.category());
bytes_transferred = buffer_copy(
buffers, in_.b.data(), read_max_);
in_.b.consume(bytes_transferred);
}
else
{
BOOST_ASSERT(in_.eof);
bytes_transferred = 0;
ec = boost::asio::error::eof;
}
++nread;
return bytes_transferred;
}
template<class MutableBufferSequence, class ReadHandler>
async_return_type<
ReadHandler, void(error_code, std::size_t)>
pipe::stream::
async_read_some(MutableBufferSequence const& buffers,
ReadHandler&& handler)
{
static_assert(is_mutable_buffer_sequence<
MutableBufferSequence>::value,
"MutableBufferSequence requirements not met");
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
BOOST_ASSERT(! in_.op);
BOOST_ASSERT(buffer_size(buffers) > 0);
async_completion<ReadHandler,
void(error_code, std::size_t)> init{handler};
if(fc_)
{
error_code ec;
if(fc_->fail(ec))
return ios_.post(bind_handler(
init.completion_handler, ec, 0));
}
{
std::unique_lock<std::mutex> lock{in_.m};
if(in_.eof)
{
lock.unlock();
++nread;
ios_.post(bind_handler(init.completion_handler,
boost::asio::error::eof, 0));
}
else if(buffer_size(buffers) == 0 ||
buffer_size(in_.b.data()) > 0)
{
auto const bytes_transferred = buffer_copy(
buffers, in_.b.data(), read_max_);
in_.b.consume(bytes_transferred);
lock.unlock();
++nread;
ios_.post(bind_handler(init.completion_handler,
error_code{}, bytes_transferred));
}
else
{
in_.op.reset(new read_op_impl<handler_type<
ReadHandler, void(error_code, std::size_t)>,
MutableBufferSequence>{*this, buffers,
init.completion_handler});
}
}
return init.result.get();
}
template<class ConstBufferSequence>
std::size_t
pipe::stream::
write_some(ConstBufferSequence const& buffers)
{
static_assert(is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
BOOST_ASSERT(! out_.eof);
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
pipe::stream::
write_some(
ConstBufferSequence const& buffers, error_code& ec)
{
static_assert(is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
BOOST_ASSERT(! out_.eof);
if(fc_ && fc_->fail(ec))
return 0;
auto const n = (std::min)(
buffer_size(buffers), write_max_);
std::unique_lock<std::mutex> lock{out_.m};
auto const bytes_transferred =
buffer_copy(out_.b.prepare(n), buffers);
out_.b.commit(bytes_transferred);
if(out_.op)
out_.op.get()->operator()();
else
out_.cv.notify_all();
lock.unlock();
++nwrite;
ec.assign(0, ec.category());
return bytes_transferred;
}
template<class ConstBufferSequence, class WriteHandler>
async_return_type<
WriteHandler, void(error_code, std::size_t)>
pipe::stream::
async_write_some(ConstBufferSequence const& buffers,
WriteHandler&& handler)
{
static_assert(is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
BOOST_ASSERT(! out_.eof);
async_completion<WriteHandler,
void(error_code, std::size_t)> init{handler};
if(fc_)
{
error_code ec;
if(fc_->fail(ec))
return ios_.post(bind_handler(
init.completion_handler, ec, 0));
}
auto const n =
(std::min)(buffer_size(buffers), write_max_);
std::unique_lock<std::mutex> lock{out_.m};
auto const bytes_transferred =
buffer_copy(out_.b.prepare(n), buffers);
out_.b.commit(bytes_transferred);
if(out_.op)
out_.op.get()->operator()();
else
out_.cv.notify_all();
lock.unlock();
++nwrite;
ios_.post(bind_handler(init.completion_handler,
error_code{}, bytes_transferred));
return init.result.get();
}
} // test
} // beast
} // boost
#endif
@@ -0,0 +1,38 @@
//
// Copyright (c) 2016-2017 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_SIG_WAIT_HPP
#define BOOST_BEAST_TEST_SIG_WAIT_HPP
#include <boost/asio.hpp>
namespace boost {
namespace beast {
namespace test {
/// Block until SIGINT or SIGTERM is received.
inline
void
sig_wait()
{
boost::asio::io_service ios;
boost::asio::signal_set signals(
ios, SIGINT, SIGTERM);
signals.async_wait(
[&](boost::system::error_code const&, int)
{
});
ios.run();
}
} // test
} // beast
} // boost
#endif
@@ -0,0 +1,625 @@
//
// Copyright (c) 2016-2017 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/async_result.hpp>
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/core/string.hpp>
#include <boost/beast/core/type_traits.hpp>
#include <boost/beast/websocket/teardown.hpp>
#include <boost/beast/test/fail_counter.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/assert.hpp>
#include <boost/throw_exception.hpp>
#include <condition_variable>
#include <limits>
#include <memory>
#include <mutex>
#include <utility>
namespace boost {
namespace beast {
namespace test {
class stream;
namespace detail {
class stream_impl
{
friend class boost::beast::test::stream;
using buffer_type = flat_buffer;
struct read_op
{
virtual ~read_op() = default;
virtual void operator()() = 0;
};
template<class Handler, class Buffers>
class read_op_impl;
struct state
{
std::mutex m;
buffer_type b;
std::condition_variable cv;
std::unique_ptr<read_op> op;
boost::asio::io_service& ios;
bool eof = false;
fail_counter* 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)();
explicit
state(
boost::asio::io_service& ios_,
fail_counter* fc_)
: ios(ios_)
, fc(fc_)
{
}
friend class stream;
};
state s0_;
state s1_;
public:
stream_impl(
boost::asio::io_service& ios,
fail_counter* fc)
: s0_(ios, fc)
, s1_(ios, fc)
{
}
};
template<class Handler, class Buffers>
class stream_impl::read_op_impl : public stream_impl::read_op
{
state& s_;
Buffers b_;
Handler h_;
public:
read_op_impl(state& s,
Buffers const& b, Handler&& h)
: s_(s)
, b_(b)
, h_(std::move(h))
{
}
read_op_impl(state& s,
Buffers const& b, Handler const& h)
: s_(s)
, b_(b)
, h_(h)
{
}
void
operator()() override;
};
template<class Handler, class Buffers>
void
stream_impl::
read_op_impl<Handler, Buffers>::
operator()()
{
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
s_.ios.post(
[&]()
{
BOOST_ASSERT(s_.op);
std::unique_lock<std::mutex> lock{s_.m};
if(s_.b.size() > 0)
{
auto const bytes_transferred = buffer_copy(
b_, s_.b.data(), s_.read_max);
s_.b.consume(bytes_transferred);
auto& s = s_;
Handler h{std::move(h_)};
s.op.reset(nullptr);
lock.unlock();
++s.nread;
s.ios.post(bind_handler(std::move(h),
error_code{}, bytes_transferred));
}
else
{
BOOST_ASSERT(s_.eof);
auto& s = s_;
Handler h{std::move(h_)};
s.op.reset(nullptr);
lock.unlock();
++s.nread;
s.ios.post(bind_handler(std::move(h),
boost::asio::error::eof, 0));
}
});
}
} // detail
//------------------------------------------------------------------------------
/** A bidirectional in-memory communication channel
An instance of this class provides a client and server
endpoint that are automatically connected to each other
similarly to a connected socket.
Test pipes are used to facilitate writing unit tests
where the behavior of the transport is tightly controlled
to help illuminate all code paths (for code coverage)
*/
class stream
{
std::shared_ptr<detail::stream_impl> impl_;
detail::stream_impl::state& in_;
detail::stream_impl::state& out_;
explicit
stream(std::shared_ptr<
detail::stream_impl> const& impl)
: impl_(impl)
, in_(impl_->s1_)
, out_(impl_->s0_)
{
}
public:
using buffer_type = flat_buffer;
~stream() = default;
stream(stream&&) = default;
stream& operator=(stream const&) = delete;
/// Constructor
explicit
stream(
boost::asio::io_service& ios)
: impl_(std::make_shared<
detail::stream_impl>(ios, nullptr))
, in_(impl_->s0_)
, out_(impl_->s1_)
{
}
/// Constructor
explicit
stream(
boost::asio::io_service& ios,
fail_counter& fc)
: impl_(std::make_shared<
detail::stream_impl>(ios, &fc))
, in_(impl_->s0_)
, out_(impl_->s1_)
{
}
/// Constructor
stream(
boost::asio::io_service& ios,
string_view s)
: impl_(std::make_shared<
detail::stream_impl>(ios, nullptr))
, in_(impl_->s0_)
, out_(impl_->s1_)
{
using boost::asio::buffer;
using boost::asio::buffer_copy;
in_.b.commit(buffer_copy(
in_.b.prepare(s.size()),
buffer(s.data(), s.size())));
}
/// Constructor
stream(
boost::asio::io_service& ios,
fail_counter& fc,
string_view s)
: impl_(std::make_shared<
detail::stream_impl>(ios, &fc))
, in_(impl_->s0_)
, out_(impl_->s1_)
{
using boost::asio::buffer;
using boost::asio::buffer_copy;
in_.b.commit(buffer_copy(
in_.b.prepare(s.size()),
buffer(s.data(), s.size())));
}
/// Return the other end of the connection
stream
remote()
{
BOOST_ASSERT(&in_ == &impl_->s0_);
return stream{impl_};
}
/// Return the `io_service` associated with the object
boost::asio::io_service&
get_io_service()
{
return in_.ios;
}
/// Set the maximum number of bytes returned by read_some
void
read_size(std::size_t n)
{
in_.read_max = n;
}
/// Set the maximum number of bytes returned by write_some
void
write_size(std::size_t n)
{
out_.write_max = n;
}
/// Direct input buffer access
buffer_type&
buffer()
{
return in_.b;
}
/// Returns a string view representing the pending input data
string_view
str() const
{
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
return {
buffer_cast<char const*>(*in_.b.data().begin()),
buffer_size(*in_.b.data().begin())};
}
/// Clear the buffer holding the input data
/*
void
clear()
{
in_.b.consume((std::numeric_limits<
std::size_t>::max)());
}
*/
/// Return the number of reads
std::size_t
nread() const
{
return in_.nread;
}
/// Return the number of writes
std::size_t
nwrite() const
{
return out_.nwrite;
}
/** Close the stream.
The other end of the pipe will see `error::eof`
after reading all the data from the buffer.
*/
template<class = void>
void
close();
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers);
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers,
error_code& ec);
template<class MutableBufferSequence, class ReadHandler>
async_return_type<
ReadHandler, void(error_code, std::size_t)>
async_read_some(MutableBufferSequence const& buffers,
ReadHandler&& handler);
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers);
template<class ConstBufferSequence>
std::size_t
write_some(
ConstBufferSequence const& buffers, error_code&);
template<class ConstBufferSequence, class WriteHandler>
async_return_type<
WriteHandler, void(error_code, std::size_t)>
async_write_some(ConstBufferSequence const& buffers,
WriteHandler&& handler);
friend
void
teardown(websocket::teardown_tag,
stream& s, boost::system::error_code& ec);
template<class TeardownHandler>
friend
void
async_teardown(websocket::teardown_tag,
stream& s, TeardownHandler&& handler);
};
//------------------------------------------------------------------------------
template<class MutableBufferSequence>
std::size_t
stream::
read_some(MutableBufferSequence const& buffers)
{
static_assert(is_mutable_buffer_sequence<
MutableBufferSequence>::value,
"MutableBufferSequence 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(is_mutable_buffer_sequence<
MutableBufferSequence>::value,
"MutableBufferSequence requirements not met");
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
BOOST_ASSERT(! in_.op);
BOOST_ASSERT(buffer_size(buffers) > 0);
if(in_.fc && in_.fc->fail(ec))
return 0;
std::unique_lock<std::mutex> lock{in_.m};
in_.cv.wait(lock,
[&]()
{
return in_.b.size() > 0 || in_.eof;
});
std::size_t bytes_transferred;
if(in_.b.size() > 0)
{
ec.assign(0, ec.category());
bytes_transferred = buffer_copy(
buffers, in_.b.data(), in_.read_max);
in_.b.consume(bytes_transferred);
}
else
{
BOOST_ASSERT(in_.eof);
bytes_transferred = 0;
ec = boost::asio::error::eof;
}
++in_.nread;
return bytes_transferred;
}
template<class MutableBufferSequence, class ReadHandler>
async_return_type<
ReadHandler, void(error_code, std::size_t)>
stream::
async_read_some(MutableBufferSequence const& buffers,
ReadHandler&& handler)
{
static_assert(is_mutable_buffer_sequence<
MutableBufferSequence>::value,
"MutableBufferSequence requirements not met");
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
BOOST_ASSERT(! in_.op);
BOOST_ASSERT(buffer_size(buffers) > 0);
async_completion<ReadHandler,
void(error_code, std::size_t)> init{handler};
if(in_.fc)
{
error_code ec;
if(in_.fc->fail(ec))
return in_.ios.post(bind_handler(
init.completion_handler, ec, 0));
}
{
std::unique_lock<std::mutex> lock{in_.m};
if(in_.eof)
{
lock.unlock();
++in_.nread;
in_.ios.post(bind_handler(init.completion_handler,
boost::asio::error::eof, 0));
}
else if(buffer_size(buffers) == 0 ||
buffer_size(in_.b.data()) > 0)
{
auto const bytes_transferred = buffer_copy(
buffers, in_.b.data(), in_.read_max);
in_.b.consume(bytes_transferred);
lock.unlock();
++in_.nread;
in_.ios.post(bind_handler(init.completion_handler,
error_code{}, bytes_transferred));
}
else
{
in_.op.reset(new
detail::stream_impl::read_op_impl<handler_type<
ReadHandler, void(error_code, std::size_t)>,
MutableBufferSequence>{in_, buffers,
init.completion_handler});
}
}
return init.result.get();
}
template<class ConstBufferSequence>
std::size_t
stream::
write_some(ConstBufferSequence const& buffers)
{
static_assert(is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
BOOST_ASSERT(! out_.eof);
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(is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
BOOST_ASSERT(! out_.eof);
if(in_.fc && in_.fc->fail(ec))
return 0;
auto const n = (std::min)(
buffer_size(buffers), out_.write_max);
std::unique_lock<std::mutex> lock{out_.m};
auto const bytes_transferred =
buffer_copy(out_.b.prepare(n), buffers);
out_.b.commit(bytes_transferred);
if(out_.op)
out_.op.get()->operator()();
else
out_.cv.notify_all();
lock.unlock();
++out_.nwrite;
ec.assign(0, ec.category());
return bytes_transferred;
}
template<class ConstBufferSequence, class WriteHandler>
async_return_type<
WriteHandler, void(error_code, std::size_t)>
stream::
async_write_some(ConstBufferSequence const& buffers,
WriteHandler&& handler)
{
static_assert(is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
BOOST_ASSERT(! out_.eof);
async_completion<WriteHandler,
void(error_code, std::size_t)> init{handler};
if(in_.fc)
{
error_code ec;
if(in_.fc->fail(ec))
return in_.ios.post(bind_handler(
init.completion_handler, ec, 0));
}
auto const n =
(std::min)(buffer_size(buffers), out_.write_max);
std::unique_lock<std::mutex> lock{out_.m};
auto const bytes_transferred =
buffer_copy(out_.b.prepare(n), buffers);
out_.b.commit(bytes_transferred);
if(out_.op)
out_.op.get()->operator()();
else
out_.cv.notify_all();
lock.unlock();
++out_.nwrite;
in_.ios.post(bind_handler(init.completion_handler,
error_code{}, bytes_transferred));
return init.result.get();
}
inline
void
teardown(websocket::teardown_tag,
stream& s, boost::system::error_code& ec)
{
if(s.in_.fc)
{
if(s.in_.fc->fail(ec))
return;
}
else
{
s.close();
ec.assign(0, ec.category());
}
}
template<class TeardownHandler>
inline
void
async_teardown(websocket::teardown_tag,
stream& s, TeardownHandler&& handler)
{
error_code ec;
if(s.in_.fc && s.in_.fc->fail(ec))
return s.get_io_service().post(
bind_handler(std::move(handler), ec));
s.close();
s.get_io_service().post(
bind_handler(std::move(handler), ec));
}
template<class>
void
stream::
close()
{
std::lock_guard<std::mutex> lock{out_.m};
if(! out_.eof)
{
out_.eof = true;
if(out_.op)
out_.op.get()->operator()();
else
out_.cv.notify_all();
}
}
} // test
} // beast
} // boost
#endif
@@ -0,0 +1,177 @@
//
// Copyright (c) 2016-2017 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_STRING_IOSTREAM_HPP
#define BOOST_BEAST_TEST_STRING_IOSTREAM_HPP
#include <boost/beast/core/async_result.hpp>
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/buffer_prefix.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/websocket/teardown.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/throw_exception.hpp>
#include <string>
namespace boost {
namespace beast {
namespace test {
/** A SyncStream and AsyncStream that reads from a string and writes to another string.
This class behaves like a socket, except that written data is
appended to a string exposed as a public data member, and when
data is read it comes from a string provided at construction.
*/
class string_iostream
{
std::string s_;
boost::asio::const_buffer cb_;
boost::asio::io_service& ios_;
std::size_t read_max_;
public:
std::string str;
string_iostream(boost::asio::io_service& ios,
std::string s, std::size_t read_max =
(std::numeric_limits<std::size_t>::max)())
: s_(std::move(s))
, cb_(boost::asio::buffer(s_))
, ios_(ios)
, read_max_(read_max)
{
}
boost::asio::io_service&
get_io_service()
{
return ios_;
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers)
{
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
read_some(MutableBufferSequence const& buffers,
error_code& ec)
{
auto const n = boost::asio::buffer_copy(
buffers, buffer_prefix(read_max_, cb_));
if(n > 0)
{
ec.assign(0, ec.category());
cb_ = cb_ + n;
}
else
{
ec = boost::asio::error::eof;
}
return n;
}
template<class MutableBufferSequence, class ReadHandler>
async_return_type<
ReadHandler, void(error_code, std::size_t)>
async_read_some(MutableBufferSequence const& buffers,
ReadHandler&& handler)
{
auto const n = boost::asio::buffer_copy(
buffers, boost::asio::buffer(s_));
error_code ec;
if(n > 0)
s_.erase(0, n);
else
ec = boost::asio::error::eof;
async_completion<ReadHandler,
void(error_code, std::size_t)> init{handler};
ios_.post(bind_handler(
init.completion_handler, ec, n));
return init.result.get();
}
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers)
{
error_code ec;
auto const n = write_some(buffers, ec);
if(ec)
BOOST_THROW_EXCEPTION(system_error{ec});
return n;
}
template<class ConstBufferSequence>
std::size_t
write_some(
ConstBufferSequence const& buffers, error_code& ec)
{
ec.assign(0, ec.category());
using boost::asio::buffer_size;
using boost::asio::buffer_cast;
auto const n = buffer_size(buffers);
str.reserve(str.size() + n);
for(boost::asio::const_buffer buffer : buffers)
str.append(buffer_cast<char const*>(buffer),
buffer_size(buffer));
return n;
}
template<class ConstBufferSequence, class WriteHandler>
async_return_type<
WriteHandler, void(error_code, std::size_t)>
async_write_some(ConstBufferSequence const& buffers,
WriteHandler&& handler)
{
error_code ec;
auto const bytes_transferred = write_some(buffers, ec);
async_completion<WriteHandler,
void(error_code, std::size_t)> init{handler};
get_io_service().post(
bind_handler(init.completion_handler, ec, bytes_transferred));
return init.result.get();
}
friend
void
teardown(websocket::teardown_tag,
string_iostream&,
boost::system::error_code& ec)
{
ec.assign(0, ec.category());
}
template<class TeardownHandler>
friend
void
async_teardown(websocket::teardown_tag,
string_iostream& stream,
TeardownHandler&& handler)
{
stream.get_io_service().post(
bind_handler(std::move(handler),
error_code{}));
}
};
} // test
} // beast
} // boost
#endif
@@ -0,0 +1,165 @@
//
// Copyright (c) 2016-2017 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_STRING_ISTREAM_HPP
#define BOOST_BEAST_TEST_STRING_ISTREAM_HPP
#include <boost/beast/core/async_result.hpp>
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/websocket/teardown.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/throw_exception.hpp>
#include <string>
namespace boost {
namespace beast {
namespace test {
/** A SyncStream and AsyncStream that reads from a string.
This class behaves like a socket, except that written data is simply
discarded, and when data is read it comes from a string provided
at construction.
*/
class string_istream
{
std::string s_;
boost::asio::const_buffer cb_;
boost::asio::io_service& ios_;
std::size_t read_max_;
public:
string_istream(boost::asio::io_service& ios,
std::string s, std::size_t read_max =
(std::numeric_limits<std::size_t>::max)())
: s_(std::move(s))
, cb_(boost::asio::buffer(s_))
, ios_(ios)
, read_max_(read_max)
{
}
boost::asio::io_service&
get_io_service()
{
return ios_;
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers)
{
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
read_some(MutableBufferSequence const& buffers,
error_code& ec)
{
auto const n = boost::asio::buffer_copy(
buffers, cb_, read_max_);
if(n > 0)
{
ec.assign(0, ec.category());
cb_ = cb_ + n;
}
else
{
ec = boost::asio::error::eof;
}
return n;
}
template<class MutableBufferSequence, class ReadHandler>
async_return_type<
ReadHandler, void(error_code, std::size_t)>
async_read_some(MutableBufferSequence const& buffers,
ReadHandler&& handler)
{
auto const n = boost::asio::buffer_copy(
buffers, boost::asio::buffer(s_));
error_code ec;
if(n > 0)
s_.erase(0, n);
else
ec = boost::asio::error::eof;
async_completion<ReadHandler,
void(error_code, std::size_t)> init{handler};
ios_.post(bind_handler(
init.completion_handler, ec, n));
return init.result.get();
}
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers)
{
error_code ec;
auto const n = write_some(buffers, ec);
if(ec)
BOOST_THROW_EXCEPTION(system_error{ec});
return n;
}
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers,
error_code& ec)
{
ec.assign(0, ec.category());
return boost::asio::buffer_size(buffers);
}
template<class ConstBuffeSequence, class WriteHandler>
async_return_type<
WriteHandler, void(error_code, std::size_t)>
async_write_some(ConstBuffeSequence const& buffers,
WriteHandler&& handler)
{
async_completion<WriteHandler,
void(error_code, std::size_t)> init{handler};
ios_.post(bind_handler(init.completion_handler,
error_code{}, boost::asio::buffer_size(buffers)));
return init.result.get();
}
friend
void
teardown(websocket::teardown_tag,
string_istream&,
boost::system::error_code& ec)
{
ec.assign(0, ec.category());
}
template<class TeardownHandler>
friend
void
async_teardown(websocket::teardown_tag,
string_istream& stream,
TeardownHandler&& handler)
{
stream.get_io_service().post(
bind_handler(std::move(handler),
error_code{}));
}
};
} // test
} // beast
} // boost
#endif
@@ -0,0 +1,153 @@
//
// Copyright (c) 2016-2017 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_STRING_OSTREAM_HPP
#define BOOST_BEAST_TEST_STRING_OSTREAM_HPP
#include <boost/beast/core/async_result.hpp>
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/buffer_prefix.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/websocket/teardown.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/throw_exception.hpp>
#include <string>
namespace boost {
namespace beast {
namespace test {
class string_ostream
{
boost::asio::io_service& ios_;
std::size_t write_max_;
public:
std::string str;
explicit
string_ostream(boost::asio::io_service& ios,
std::size_t write_max =
(std::numeric_limits<std::size_t>::max)())
: ios_(ios)
, write_max_(write_max)
{
}
boost::asio::io_service&
get_io_service()
{
return ios_;
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers)
{
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
read_some(MutableBufferSequence const&,
error_code& ec)
{
ec = boost::asio::error::eof;
return 0;
}
template<class MutableBufferSequence, class ReadHandler>
async_return_type<
ReadHandler, void(error_code, std::size_t)>
async_read_some(MutableBufferSequence const&,
ReadHandler&& handler)
{
async_completion<ReadHandler,
void(error_code, std::size_t)> init{handler};
ios_.post(bind_handler(init.completion_handler,
boost::asio::error::eof, 0));
return init.result.get();
}
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers)
{
error_code ec;
auto const n = write_some(buffers, ec);
if(ec)
BOOST_THROW_EXCEPTION(system_error{ec});
return n;
}
template<class ConstBufferSequence>
std::size_t
write_some(
ConstBufferSequence const& buffers, error_code& ec)
{
ec.assign(0, ec.category());
using boost::asio::buffer_size;
using boost::asio::buffer_cast;
auto const n =
(std::min)(buffer_size(buffers), write_max_);
str.reserve(str.size() + n);
for(boost::asio::const_buffer buffer :
buffer_prefix(n, buffers))
str.append(buffer_cast<char const*>(buffer),
buffer_size(buffer));
return n;
}
template<class ConstBufferSequence, class WriteHandler>
async_return_type<
WriteHandler, void(error_code, std::size_t)>
async_write_some(ConstBufferSequence const& buffers,
WriteHandler&& handler)
{
error_code ec;
auto const bytes_transferred = write_some(buffers, ec);
async_completion<WriteHandler,
void(error_code, std::size_t)> init{handler};
get_io_service().post(
bind_handler(init.completion_handler, ec, bytes_transferred));
return init.result.get();
}
friend
void
teardown(websocket::teardown_tag,
string_ostream&,
boost::system::error_code& ec)
{
ec.assign(0, ec.category());
}
template<class TeardownHandler>
friend
void
async_teardown(websocket::teardown_tag,
string_ostream& stream,
TeardownHandler&& handler)
{
stream.get_io_service().post(
bind_handler(std::move(handler),
error_code{}));
}
};
} // test
} // beast
} // boost
#endif
@@ -0,0 +1,170 @@
//
// Copyright (c) 2016-2017 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_TEST_ALLOCATOR_HPP
#define BOOST_BEAST_TEST_TEST_ALLOCATOR_HPP
#include <atomic>
#include <cstddef>
#include <memory>
namespace boost {
namespace beast {
namespace test {
struct test_allocator_info
{
std::size_t id;
std::size_t ncopy = 0;
std::size_t nmove = 0;
std::size_t nmassign = 0;
std::size_t ncpassign = 0;
std::size_t nselect = 0;
test_allocator_info()
: id([]
{
static std::atomic<std::size_t> sid(0);
return ++sid;
}())
{
}
};
template<class T, bool Equal, bool Assign, bool Move, bool Swap, bool Select>
class test_allocator;
template<class T, bool Equal, bool Assign, bool Move, bool Swap, bool Select>
struct test_allocator_base
{
};
template<class T, bool Equal, bool Assign, bool Move, bool Swap>
struct test_allocator_base<T, Equal, Assign, Move, Swap, true>
{
static
test_allocator<T, Equal, Assign, Move, Swap, true>
select_on_container_copy_construction(test_allocator<
T, Equal, Assign, Move, Swap, true> const& a)
{
return test_allocator<T, Equal, Assign, Move, Swap, true>{};
}
};
template<class T, bool Equal, bool Assign, bool Move, bool Swap, bool Select>
class test_allocator : public test_allocator_base<
T, Equal, Assign, Move, Swap, Select>
{
std::shared_ptr<test_allocator_info> info_;
template<class, bool, bool, bool, bool, bool>
friend class test_allocator;
public:
using value_type = T;
using propagate_on_container_copy_assignment =
std::integral_constant<bool, Assign>;
using propagate_on_container_move_assignment =
std::integral_constant<bool, Move>;
using propagate_on_container_swap =
std::integral_constant<bool, Swap>;
template<class U>
struct rebind
{
using other = test_allocator<U, Equal, Assign, Move, Swap, Select>;
};
test_allocator()
: info_(std::make_shared<test_allocator_info>())
{
}
test_allocator(test_allocator const& u) noexcept
: info_(u.info_)
{
++info_->ncopy;
}
template<class U>
test_allocator(test_allocator<U,
Equal, Assign, Move, Swap, Select> const& u) noexcept
: info_(u.info_)
{
++info_->ncopy;
}
test_allocator(test_allocator&& t)
: info_(t.info_)
{
++info_->nmove;
}
test_allocator&
operator=(test_allocator const& u) noexcept
{
info_ = u.info_;
++info_->ncpassign;
return *this;
}
test_allocator&
operator=(test_allocator&& u) noexcept
{
info_ = u.info_;
++info_->nmassign;
return *this;
}
value_type*
allocate(std::size_t n)
{
return static_cast<value_type*>(
::operator new (n*sizeof(value_type)));
}
void
deallocate(value_type* p, std::size_t) noexcept
{
::operator delete(p);
}
bool
operator==(test_allocator const& other) const
{
return id() == other.id() || Equal;
}
bool
operator!=(test_allocator const& other) const
{
return ! this->operator==(other);
}
std::size_t
id() const
{
return info_->id;
}
test_allocator_info const*
operator->() const
{
return info_.get();
}
};
} // test
} // beast
} // boost
#endif
@@ -0,0 +1,138 @@
//
// Copyright (c) 2016-2017 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_YIELD_TO_HPP
#define BOOST_BEAST_TEST_YIELD_TO_HPP
#include <boost/asio/io_service.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/optional.hpp>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <thread>
#include <vector>
namespace boost {
namespace beast {
namespace test {
/** Mix-in to support tests using asio coroutines.
Derive from this class and use yield_to to launch test
functions inside coroutines. This is handy for testing
asynchronous asio code.
*/
class enable_yield_to
{
protected:
boost::asio::io_service ios_;
private:
boost::optional<boost::asio::io_service::work> work_;
std::vector<std::thread> threads_;
std::mutex m_;
std::condition_variable cv_;
std::size_t running_ = 0;
public:
/// The type of yield context passed to functions.
using yield_context =
boost::asio::yield_context;
explicit
enable_yield_to(std::size_t concurrency = 1)
: work_(ios_)
{
threads_.reserve(concurrency);
while(concurrency--)
threads_.emplace_back(
[&]{ ios_.run(); });
}
~enable_yield_to()
{
work_ = boost::none;
for(auto& t : threads_)
t.join();
}
/// Return the `io_service` associated with the object
boost::asio::io_service&
get_io_service()
{
return ios_;
}
/** Run one or more functions, each in a coroutine.
This call will block until all coroutines terminate.
Each functions should have this signature:
@code
void f(yield_context);
@endcode
@param fn... One or more functions to invoke.
*/
#if BOOST_BEAST_DOXYGEN
template<class... FN>
void
yield_to(FN&&... fn)
#else
template<class F0, class... FN>
void
yield_to(F0&& f0, FN&&... fn);
#endif
private:
void
spawn()
{
}
template<class F0, class... FN>
void
spawn(F0&& f, FN&&... fn);
};
template<class F0, class... FN>
void
enable_yield_to::
yield_to(F0&& f0, FN&&... fn)
{
running_ = 1 + sizeof...(FN);
spawn(f0, fn...);
std::unique_lock<std::mutex> lock{m_};
cv_.wait(lock, [&]{ return running_ == 0; });
}
template<class F0, class... FN>
inline
void
enable_yield_to::
spawn(F0&& f, FN&&... fn)
{
boost::asio::spawn(ios_,
[&](yield_context yield)
{
f(yield);
std::lock_guard<std::mutex> lock{m_};
if(--running_ == 0)
cv_.notify_all();
}
, boost::coroutines::attributes(2 * 1024 * 1024));
spawn(fn...);
}
} // test
} // beast
} // boost
#endif
@@ -0,0 +1,59 @@
//
// Copyright (c) 2016-2017 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
@@ -0,0 +1,95 @@
//
// Copyright (c) 2016-2017 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
@@ -0,0 +1,130 @@
//
// Copyright (c) 2016-2017 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 <streambuf>
#include <string>
#ifdef BOOST_WINDOWS
#include <boost/detail/winapi/basic_types.hpp>
#include <boost/detail/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::detail::winapi::OutputDebugStringA(s);
os_ << s;
}
void write(wchar_t const* s)
{
if(dbg_)
boost::detail::winapi::OutputDebugStringW(s);
os_ << s;
}
public:
explicit
dstream_buf(ostream& os)
: os_(os)
, dbg_(boost::detail::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
@@ -0,0 +1,55 @@
//
// Copyright (c) 2016-2017 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/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
@@ -0,0 +1,87 @@
//
// Copyright (c) 2016-2017 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/unit_test/amount.hpp>
#include <boost/beast/unit_test/dstream.hpp>
#include <boost/beast/unit_test/global_suites.hpp>
#include <boost/beast/unit_test/match.hpp>
#include <boost/beast/unit_test/reporter.hpp>
#include <boost/beast/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);
#if 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;
}
@@ -0,0 +1,177 @@
//
// Copyright (c) 2016-2017 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/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
@@ -0,0 +1,96 @@
//
// Copyright (c) 2016-2017 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/unit_test/results.hpp>
#include <boost/beast/unit_test/runner.hpp>
namespace boost {
namespace beast {
namespace unit_test {
/** A test runner that stores the results. */
class recorder : public runner
{
private:
results m_results;
suite_results m_suite;
case_results m_case;
public:
recorder() = default;
recorder(recorder const&) = default;
recorder& operator=(recorder const&) = 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
@@ -0,0 +1,297 @@
//
// Copyright (c) 2016-2017 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/unit_test/amount.hpp>
#include <boost/beast/unit_test/recorder.hpp>
#include <boost/optional.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())
{
if(top.size() == max_top)
top.resize(top.size() - 1);
top.emplace(iter, r.name, elapsed);
}
else if(top.size() < max_top)
{
top.emplace_back(r.name, elapsed);
}
}
}
//------------------------------------------------------------------------------
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
@@ -0,0 +1,246 @@
//
// Copyright (c) 2016-2017 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/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
@@ -0,0 +1,292 @@
//
// Copyright (c) 2016-2017 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/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 argumnet 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
@@ -0,0 +1,690 @@
//
// Copyright (c) 2016-2017 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/unit_test/runner.hpp>
#include <boost/filesystem.hpp>
#include <boost/throw_exception.hpp>
#include <ostream>
#include <sstream>
#include <string>
namespace boost {
namespace beast {
namespace unit_test {
namespace detail {
template<class String>
static
std::string
make_reason(String const& reason,
char const* file, int line)
{
std::string s(reason);
if(! s.empty())
s.append(": ");
namespace fs = boost::filesystem;
s.append(fs::path{file}.filename().string());
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_EXPECT
/** Check a precondition.
If the condition is false, the file and line number are reported.
*/
#define BEAST_EXPECT(cond) 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) ? (pass(), true) : \
(fail((reason), __FILE__, __LINE__), false))
#endif
} // unit_test
} // beast
} // boost
//------------------------------------------------------------------------------
// detail:
// This inserts the suite with the given manual flag
#define BOOST_BEAST_DEFINE_TESTSUITE_INSERT(Class,Module,Library,manual) \
static 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 BOOST_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 BOOST_BEAST_DEFINE_TESTSUITE is user defined, this macro
has no effect.
*/
#ifndef BOOST_BEAST_NO_UNIT_TEST_INLINE
#define BOOST_BEAST_NO_UNIT_TEST_INLINE 0
#endif
/** Define a unit test suite.
Class The type representing the class being tested.
Module Identifies the module.
Library Identifies the library.
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 BOOST_BEAST_NO_UNIT_TEST_INLINE
#define BOOST_BEAST_DEFINE_TESTSUITE(Class,Module,Library)
#else
#include <boost/beast/unit_test/global_suites.hpp>
#define BOOST_BEAST_DEFINE_TESTSUITE(Class,Module,Library) \
BOOST_BEAST_DEFINE_TESTSUITE_INSERT(Class,Module,Library,false)
#define BOOST_BEAST_DEFINE_TESTSUITE_MANUAL(Class,Module,Library) \
BOOST_BEAST_DEFINE_TESTSUITE_INSERT(Class,Module,Library,true)
#endif
#endif
//------------------------------------------------------------------------------
#endif
@@ -0,0 +1,126 @@
//
// Copyright (c) 2016-2017 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
@@ -0,0 +1,81 @@
//
// Copyright (c) 2016-2017 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/unit_test/suite_info.hpp>
#include <boost/beast/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
@@ -0,0 +1,128 @@
//
// Copyright (c) 2016-2017 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/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