//
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//

// Test that header file is self-contained.
#include <boost/beast/core/basic_stream.hpp>

#include "stream_tests.hpp"

#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/core/stream_traits.hpp>
#include <boost/beast/core/string.hpp>
#include <boost/beast/core/tcp_stream.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/empty_body.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/http/write.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/write.hpp>
#include <boost/optional.hpp>
#include <array>
#include <thread>

#if BOOST_ASIO_HAS_CO_AWAIT
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/use_awaitable.hpp>
#endif


namespace boost {
namespace beast {

namespace {

template<class Executor = net::io_context::executor_type>
class test_executor
{
public:
    // VFALCO These need to be atomic or something
    struct info
    {
        int dispatch = 0;
        int post = 0;
        int defer = 0;
        int work = 0;
        int total = 0;
    };

private:
    struct state
    {
        Executor ex;
        info info_;

        state(Executor const& ex_)
            : ex(ex_)
        {
        }
    };

    std::shared_ptr<state> sp_;

public:
    test_executor(test_executor const&) = default;
    test_executor& operator=(test_executor const&) = default;

    explicit
    test_executor(Executor const& ex)
        : sp_(std::make_shared<state>(ex))
    {
    }

    decltype(sp_->ex.context())
    context() const noexcept
    {
        return sp_->ex.context();
    }

    info&
    operator*() noexcept
    {
        return sp_->info_;
    }

    info*
    operator->() noexcept
    {
        return &sp_->info_;
    }

    void
    on_work_started() const noexcept
    {
        ++sp_->info_.work;
    }

    void
    on_work_finished() const noexcept
    {
    }

    template<class F, class A>
    void
    dispatch(F&& f, A const& a)
    {
        ++sp_->info_.dispatch;
        ++sp_->info_.total;
        sp_->ex.dispatch(
            std::forward<F>(f), a);
    }

    template<class F, class A>
    void
    post(F&& f, A const& a)
    {
        ++sp_->info_.post;
        ++sp_->info_.total;
        sp_->ex.post(
            std::forward<F>(f), a);
    }

    template<class F, class A>
    void
    defer(F&& f, A const& a)
    {
        ++sp_->info_.defer;
        ++sp_->info_.total;
        sp_->ex.defer(
            std::forward<F>(f), a);
    }
};

struct test_acceptor
{
    net::io_context ioc;
    net::ip::tcp::acceptor a;
    net::ip::tcp::endpoint ep;

    test_acceptor()
        : a(ioc)
        , ep(net::ip::make_address_v4("127.0.0.1"), 0)
    {
        a.open(ep.protocol());
        a.set_option(
            net::socket_base::reuse_address(true));
        a.bind(ep);
        a.listen(net::socket_base::max_listen_connections);
        ep = a.local_endpoint();
        a.async_accept(
            [](error_code, net::ip::tcp::socket)
            {
            });
    }
};

class test_server
{
    string_view s_;
    std::ostream& log_;
    net::io_context ioc_;
    net::ip::tcp::acceptor acceptor_;
    net::ip::tcp::socket socket_;
    std::thread t_;

    void
    fail(error_code const& ec, string_view what)
    {
        if(ec != net::error::operation_aborted)
            log_ << what << ": " << ec.message() << "\n";
    }

public:
    test_server(
        string_view s,
        net::ip::tcp::endpoint ep,
        std::ostream& log)
        : s_(s)
        , log_(log)
        , ioc_(1)
        , acceptor_(ioc_)
        , socket_(ioc_)
    {
        boost::system::error_code ec;

        acceptor_.open(ep.protocol(), ec);
        if(ec)
        {
            fail(ec, "open");
            return;
        }

        acceptor_.set_option(
            net::socket_base::reuse_address(true), ec);
        if(ec)
        {
            fail(ec, "set_option");
            return;
        }

        acceptor_.bind(ep, ec);
        if(ec)
        {
            fail(ec, "bind");
            return;
        }

        acceptor_.listen(
            net::socket_base::max_listen_connections, ec);
        if(ec)
        {
            fail(ec, "listen");
            return;
        }

        acceptor_.async_accept(socket_,
            [this](error_code ec)
            {
                this->on_accept(ec);
            });

        t_ = std::thread(
            [this]
            {
                ioc_.run();
            });
    }

    ~test_server()
    {
        ioc_.stop();
        t_.join();
    }

    net::ip::tcp::endpoint
    local_endpoint() const noexcept
    {
        return acceptor_.local_endpoint();
    }

private:
    class session
        : public std::enable_shared_from_this<session>
    {
        string_view s_;
        net::ip::tcp::socket socket_;

    public:
        session(
            string_view s,
            net::ip::tcp::socket sock,
            std::ostream&)
            : s_(s)
            , socket_(std::move(sock))
        {
        }

        void
        run()
        {
            if(s_.empty())
                socket_.async_wait(
                    net::socket_base::wait_read,
                    bind_front_handler(
                        &session::on_read,
                        shared_from_this()));
            else
                net::async_write(
                    socket_,
                    net::const_buffer(s_.data(), s_.size()),
                    bind_front_handler(
                        &session::on_write,
                        shared_from_this()));
        }

    protected:
        void
        on_read(error_code const&)
        {
        }

        void
        on_write(error_code const&, std::size_t)
        {
        }
    };

    void
    on_accept(error_code const& ec)
    {
        if(! acceptor_.is_open())
            return;
        if(ec)
            fail(ec, "accept");
        else
            std::make_shared<session>(
                s_, std::move(socket_), log_)->run();
        acceptor_.async_accept(socket_,
            [this](error_code ec)
            {
                this->on_accept(ec);
            });
    }
};

} // (anon)

class basic_stream_test
    : public beast::unit_test::suite
{
public:
    using tcp = net::ip::tcp;
    using executor = net::io_context::executor_type;
    using strand = net::strand<executor>;

    //--------------------------------------------------------------------------

    void
    testSpecialMembers()
    {
        net::io_context ioc;

        // net::io_context::executor_type

        {
            auto ex = ioc.get_executor();
            basic_stream<tcp, executor> s1(ioc);
            basic_stream<tcp, executor> s2(ex);
            basic_stream<tcp, executor> s3(ioc, tcp::v4());
            basic_stream<tcp, executor> s4(std::move(s1));
            s2.socket() =
                net::basic_stream_socket<tcp, executor>(ioc);
            BEAST_EXPECT(s1.get_executor() == ex);
            BEAST_EXPECT(s2.get_executor() == ex);
            BEAST_EXPECT(s3.get_executor() == ex);
            BEAST_EXPECT(s4.get_executor() == ex);

            BEAST_EXPECT((! static_cast<
                basic_stream<tcp, executor> const&>(
                    s2).socket().is_open()));

            test_sync_stream<
                basic_stream<
                    tcp, net::io_context::executor_type>>();

            test_async_stream<
                basic_stream<
                    tcp, net::io_context::executor_type>>();
        }

        // net::io_context::strand

        {
            auto ex = net::make_strand(ioc);
            basic_stream<tcp, strand> s1(ex);
            basic_stream<tcp, strand> s2(ex, tcp::v4());
            basic_stream<tcp, strand> s3(std::move(s1));
            BEAST_EXPECT(s1.get_executor() == ex);
            BEAST_EXPECT(s2.get_executor() == ex);
            BEAST_EXPECT(s3.get_executor() == ex);

            test_sync_stream<
                basic_stream<
                    tcp, strand>>();

            test_async_stream<
                basic_stream<
                    tcp, strand>>();
        }

        // layers

        {
            net::socket_base::keep_alive opt;
            tcp_stream s(ioc);
            s.socket().open(tcp::v4());
            s.socket().get_option(opt);
            BEAST_EXPECT(! opt.value());
            opt = true;
            s.socket().set_option(opt);
            opt = false;
            BEAST_EXPECT(! opt.value());
        }

        // rate policies

        {
            basic_stream<tcp,
                net::io_context::executor_type,
                simple_rate_policy> s(ioc);
        }

        {
            basic_stream<tcp,
                net::io_context::executor_type,
                simple_rate_policy> s(
                    simple_rate_policy{}, ioc);
        }

        {
            basic_stream<tcp,
                net::io_context::executor_type,
                unlimited_rate_policy> s(ioc);
        }

        {
            basic_stream<tcp,
                net::io_context::executor_type,
                unlimited_rate_policy> s(
                    unlimited_rate_policy{}, ioc);
        }
    }

    class handler
    {
        boost::optional<error_code> ec_;
        std::size_t n_;

    public:
        handler(error_code ec, std::size_t n)
            : ec_(ec)
            , n_(n)
        {
        }

        handler(handler&& other)
            : ec_(other.ec_)
            , n_(boost::exchange(other.n_,
                (std::numeric_limits<std::size_t>::max)()))
        {
        }

        ~handler()
        {
            BEAST_EXPECT(
                n_ == (std::numeric_limits<std::size_t>::max)());
        }

        void
        operator()(error_code const& ec, std::size_t n)
        {
            BEAST_EXPECTS(ec == ec_, ec.message());
            BEAST_EXPECT(n == n_);
            n_ = (std::numeric_limits<std::size_t>::max)();
        }
    };

    void
    testRead()
    {
        using stream_type = basic_stream<tcp,
            net::io_context::executor_type>;

        char buf[4];
        net::io_context ioc;
        std::memset(buf, 0, sizeof(buf));
        net::mutable_buffer mb(buf, sizeof(buf));
        auto const ep = net::ip::tcp::endpoint(
            net::ip::make_address("127.0.0.1"), 0);

        // read_some

        {
            error_code ec;
            stream_type s(ioc, tcp::v4());
            BEAST_EXPECT(s.read_some(net::mutable_buffer{}) == 0);
            BEAST_EXPECT(s.read_some(net::mutable_buffer{}, ec) == 0);
            BEAST_EXPECTS(! ec, ec.message());
        }

        // async_read_some

        {
            // success
            test_server srv("*", ep, log);
            stream_type s(ioc);
            s.socket().connect(srv.local_endpoint());
            s.expires_never();
            s.async_read_some(mb, handler({}, 1));
            ioc.run();
            ioc.restart();
        }

        {
            // success, with timeout
            test_server srv("*", ep, log);
            stream_type s(ioc);
            s.socket().connect(srv.local_endpoint());
            s.expires_after(std::chrono::seconds(30));
            s.async_read_some(mb, handler({}, 1));
            ioc.run();
            ioc.restart();
        }

        {
            // empty buffer
            test_server srv("*", ep, log);
            stream_type s(ioc);
            s.socket().connect(srv.local_endpoint());
            s.expires_never();
            s.async_read_some(
                net::mutable_buffer{}, handler({}, 0));
            ioc.run();
            ioc.restart();
        }

        {
            // empty buffer, timeout
            test_server srv("*", ep, log);
            stream_type s(ioc);
            s.socket().connect(srv.local_endpoint());
            s.expires_after(std::chrono::seconds(0));
            s.async_read_some(net::mutable_buffer{},
                handler(error::timeout, 0));
            ioc.run();
            ioc.restart();
        }

        {
            // expires_after
            test_server srv("", ep, log);
            stream_type s(ioc);
            s.socket().connect(srv.local_endpoint());
            s.expires_after(std::chrono::seconds(0));
            s.async_read_some(mb, handler(error::timeout, 0));
            ioc.run();
            ioc.restart();
        }

        {
            // expires_at
            test_server srv("", ep, log);
            stream_type s(ioc);
            s.socket().connect(srv.local_endpoint());
            s.expires_at(std::chrono::steady_clock::now());
            s.async_read_some(mb, handler(error::timeout, 0));
            ioc.run();
            ioc.restart();
        }

        {
            // stream destroyed
            test_server srv("", ep, log);
            {
                auto s = net::detached.as_default_on(stream_type(ioc));
                s.socket().connect(srv.local_endpoint());
                s.expires_after(std::chrono::seconds(0));
                s.async_read_some(mb);
            }
            ioc.run();
            ioc.restart();
        }

        {
            // stale timer
            test_acceptor a;
            auto s = net::detached.as_default_on(stream_type(ioc));
            s.expires_after(std::chrono::milliseconds(50));
            s.async_read_some(mb);
            std::this_thread::sleep_for(
                std::chrono::milliseconds(100));
            ioc.run();
            ioc.restart();
        }

        // abandoned operation
        {
            stream_type s(ioc);
            s.async_read_some(net::mutable_buffer{},
                [](error_code, std::size_t)
                {
                    BEAST_FAIL();
                });
        }
    }

    void
    testWrite()
    {
        using stream_type = basic_stream<tcp,
            net::io_context::executor_type>;

        char buf[4];
        net::io_context ioc;
        std::memset(buf, 0, sizeof(buf));
        net::const_buffer cb(buf, sizeof(buf));
        auto const ep = net::ip::tcp::endpoint(
            net::ip::make_address("127.0.0.1"), 0);

        // write_some

        {
            error_code ec;
            stream_type s(ioc, tcp::v4());
            BEAST_EXPECT(s.write_some(net::const_buffer{}) == 0);
            BEAST_EXPECT(s.write_some(net::const_buffer{}, ec) == 0);
            BEAST_EXPECTS(! ec, ec.message());
        }

        // async_write_some

        {
            // success
            test_server srv("*", ep, log);
            stream_type s(ioc);
            s.socket().connect(srv.local_endpoint());
            s.expires_never();
            s.async_write_some(cb, handler({}, 4));
            ioc.run();
            ioc.restart();
        }

        {
            // success, with timeout
            test_server srv("*", ep, log);
            stream_type s(ioc);
            s.socket().connect(srv.local_endpoint());
            s.expires_after(std::chrono::seconds(30));
            s.async_write_some(cb, handler({}, 4));
            ioc.run();
            ioc.restart();
        }

        {
            // empty buffer
            test_server srv("*", ep, log);
            stream_type s(ioc);
            s.socket().connect(srv.local_endpoint());
            s.expires_never();
            s.async_write_some(
                net::const_buffer{}, handler({}, 0));
            ioc.run();
            ioc.restart();
        }

        {
            // empty buffer, timeout
            test_server srv("*", ep, log);
            stream_type s(ioc);
            s.socket().connect(srv.local_endpoint());
            s.expires_after(std::chrono::seconds(0));
            s.async_write_some(net::const_buffer{},
                handler(error::timeout, 0));
            ioc.run();
            ioc.restart();
        }

        {
            // non-empty buffer, timeout
            test_server srv("*", ep, log);
            stream_type s(ioc);
            s.socket().connect(srv.local_endpoint());
            s.expires_after(std::chrono::seconds(0));
            s.async_write_some(cb, handler(error::timeout, 0));
            ioc.run();
            ioc.restart();
        }

        // abandoned operation
        {
            stream_type s(ioc);
            s.async_write_some(cb,
                [](error_code, std::size_t)
                {
                    BEAST_FAIL();
                });
        }
    }

    void
    testConnect()
    {
        using stream_type = basic_stream<tcp,
            net::io_context::executor_type>;

        struct range
        {
            tcp::endpoint ep;

            using iterator =
                tcp::endpoint const*;

            // VFALCO This is here because asio mistakenly requires it
            using const_iterator =
                tcp::endpoint const*;

            iterator begin() const noexcept
            {
                return &ep;
            }

            // VFALCO need to use const_iterator to silence
            //        warning about unused types
            const_iterator end() const noexcept
            {
                return begin() + 1;
            }
        };

        class connect_handler
        {
            bool pass_ = false;
            boost::optional<error_code> expected_ = {};

        public:
            ~connect_handler()
            {
                BEAST_EXPECT(pass_);
            }

            connect_handler()
                : expected_(error_code{})
            {
            }

            explicit
            connect_handler(error_code expected)
                : expected_(expected)
            {
            }

            explicit
            connect_handler(boost::none_t)
            {
            }

            connect_handler(connect_handler&& other)
                : pass_(boost::exchange(other.pass_, true))
                , expected_(other.expected_)
            {
            }

            void operator()(error_code ec)
            {
                pass_ = true;
                if(expected_)
                    BEAST_EXPECTS(
                        ec == expected_, ec.message());
            }
        };

        struct range_handler
        {
            bool pass = false;

            range_handler() = default;

            range_handler(range_handler&& other)
                : pass(boost::exchange(other.pass, true))
            {
            }

            ~range_handler()
            {
                BEAST_EXPECT(pass);
            }

            void operator()(error_code ec, tcp::endpoint)
            {
                pass = true;
                BEAST_EXPECTS(! ec, ec.message());
            }
        };

        struct iterator_handler
        {
            bool pass = false;

            iterator_handler() = default;

            iterator_handler(iterator_handler&& other)
                : pass(boost::exchange(other.pass, true))
            {
            }

            ~iterator_handler()
            {
                BEAST_EXPECT(pass);
            }

            void operator()(error_code ec, tcp::endpoint const*)
            {
                pass = true;
                BEAST_EXPECTS(! ec, ec.message());
            }
        };

        struct connect_condition
        {
            bool operator()(error_code, tcp::endpoint) const
            {
                return true;
            };
        };

        range r;
        net::io_context ioc;
        connect_condition cond;

        // connect (member)

        {
            test_acceptor a;
            stream_type s(ioc);
            error_code ec;
            s.connect(a.ep);
            s.socket().close();
            s.connect(a.ep, ec);
            BEAST_EXPECTS(! ec, ec.message());
        }

        // connect

        {
            test_acceptor a;
            stream_type s(ioc);
            error_code ec;
            r.ep = a.ep;
            s.connect(r);
            s.socket().close();
            s.connect(r, ec);
            BEAST_EXPECTS(! ec, ec.message());
        }

        {
            test_acceptor a;
            stream_type s(ioc);
            error_code ec;
            r.ep = a.ep;
            s.connect(r, cond);
            s.socket().close();
            s.connect(r, cond, ec);
            BEAST_EXPECTS(! ec, ec.message());
        }

        {
            test_acceptor a;
            stream_type s(ioc);
            error_code ec;
            r.ep = a.ep;
            s.connect(r.begin(), r.end());
            s.socket().close();
            s.connect(r.begin(), r.end(), ec);
            BEAST_EXPECTS(! ec, ec.message());
        }

        {
            test_acceptor a;
            stream_type s(ioc);
            error_code ec;
            r.ep = a.ep;
            s.connect(r.begin(), r.end(), cond);
            s.socket().close();
            s.connect(r.begin(), r.end(), cond, ec);
            BEAST_EXPECTS(! ec, ec.message());
        }

        // async_connect (member)

        {
            test_acceptor a;
            stream_type s(ioc);
            s.expires_never();
            s.async_connect(a.ep, connect_handler{});
            ioc.run();
            ioc.restart();
            s.socket().close();
            s.expires_after(std::chrono::seconds(30));
            s.async_connect(a.ep, connect_handler{});
            ioc.run();
            ioc.restart();
        }

        // async_connect

        {
            test_acceptor a;
            stream_type s(ioc);
            r.ep = a.ep;
            s.expires_never();
            s.async_connect(r, range_handler{});
            ioc.run();
            ioc.restart();
            s.socket().close();
            s.expires_after(std::chrono::seconds(30));
            s.async_connect(r, range_handler{});
            ioc.run();
            ioc.restart();
        }

        {
            test_acceptor a;
            stream_type s(ioc);
            r.ep = a.ep;
            s.expires_never();
            s.async_connect(r, cond, range_handler{});
            ioc.run();
            ioc.restart();
            s.socket().close();
            s.expires_after(std::chrono::seconds(30));
            s.async_connect(r, cond, range_handler{});
            ioc.run();
            ioc.restart();
        }

        {
            test_acceptor a;
            stream_type s(ioc);
            r.ep = a.ep;
            s.expires_never();
            s.async_connect(r.begin(), r.end(),
                iterator_handler{});
            ioc.run();
            ioc.restart();
            s.socket().close();
            s.expires_after(std::chrono::seconds(30));
            s.async_connect(r.begin(), r.end(),
                iterator_handler{});
            ioc.run();
            ioc.restart();
        }

        {
            test_acceptor a;
            stream_type s(ioc);
            r.ep = a.ep;
            s.expires_never();
            s.async_connect(r.begin(), r.end(), cond,
                iterator_handler{});
            ioc.run();
            ioc.restart();
            s.socket().close();
            s.expires_after(std::chrono::seconds(30));
            s.async_connect(r.begin(), r.end(), cond,
                iterator_handler{});
            ioc.run();
            ioc.restart();
        }
#if 0
        // use_future

        BEAST_EXPECT(static_cast<std::future<
            tcp::endpoint>(*)(stream_type&,
                std::array<tcp::endpoint, 2> const&,
                net::use_future_t<>&&)>(
                    &beast::async_connect));

        BEAST_EXPECT(static_cast<std::future<
            tcp::endpoint>(*)(stream_type&,
                std::array<tcp::endpoint, 2> const&,
                connect_condition const&,
                net::use_future_t<>&&)>(
                    &beast::async_connect));

        BEAST_EXPECT(static_cast<std::future<
            tcp::endpoint const*>(*)(stream_type&,
                tcp::endpoint const*,
                tcp::endpoint const*,
                net::use_future_t<>&&)>(
                    &beast::async_connect));

        BEAST_EXPECT(static_cast<std::future<
            tcp::endpoint const*>(*)(stream_type&,
                tcp::endpoint const*,
                tcp::endpoint const*,
                connect_condition const&,
                net::use_future_t<>&&)>(
                    &beast::async_connect));

        // yield_context

        BEAST_EXPECT(static_cast<
            tcp::endpoint(*)(stream_type&,
                std::array<tcp::endpoint, 2> const&,
                net::yield_context&&)>(
                    &beast::async_connect));

        BEAST_EXPECT(static_cast<
            tcp::endpoint(*)(stream_type&,
                std::array<tcp::endpoint, 2> const&,
                connect_condition const&,
                net::yield_context&&)>(
                    &beast::async_connect));

        BEAST_EXPECT(static_cast<
            tcp::endpoint const*(*)(stream_type&,
                tcp::endpoint const*,
                tcp::endpoint const*,
                net::yield_context&&)>(
                    &beast::async_connect));

        BEAST_EXPECT(static_cast<
            tcp::endpoint const*(*)(stream_type&,
                tcp::endpoint const*,
                tcp::endpoint const*,
                connect_condition const&,
                net::yield_context&&)>(
                    &beast::async_connect));
#endif

        //
        // async_connect timeout
        //

        {
            // normal timeout
            // Requires timeout happen before ECONNREFUSED 
            stream_type s(ioc);
            auto const ep = net::ip::tcp::endpoint(
            #if 1
                // This address _should_ be unconnectible
                net::ip::make_address("72.5.65.111"), 1);
            #else
                // On Travis ECONNREFUSED happens before the timeout
                net::ip::make_address("127.0.0.1"), 1);
            #endif
            s.expires_after(std::chrono::seconds(0));
            s.async_connect(ep, connect_handler{error::timeout});
            ioc.run_for(std::chrono::seconds(1));
            ioc.restart();
        }

        {
            // stream destroyed
            {
                stream_type s(ioc);
                auto const ep = net::ip::tcp::endpoint(
                    net::ip::make_address("127.0.0.1"), 1);
                s.expires_after(std::chrono::seconds(0));
                s.async_connect(ep, connect_handler{boost::none});
            }
            ioc.run();
            ioc.restart();
        }

        {
            // stale timer
            test_acceptor a;
            stream_type s(ioc);
            s.expires_after(std::chrono::milliseconds(50));
            s.async_connect(a.ep, connect_handler{});
            std::this_thread::sleep_for(
                std::chrono::milliseconds(100));
            ioc.run();
            ioc.restart();
        }

        // abandoned operation
        {
            stream_type s(ioc);
            net::ip::tcp::endpoint ep(
                net::ip::make_address_v4("127.0.0.1"), 1);
            s.async_connect(ep,
                [](error_code)
                {
                    BEAST_FAIL();
                });
        }
    }

    void
    testMembers()
    {
        using stream_type = basic_stream<tcp,
            net::io_context::executor_type>;

        class handler
        {
            bool pass_ = false;
            boost::optional<error_code> expected_ = {};

        public:
            ~handler()
            {
                BEAST_EXPECT(pass_);
            }

            handler()
                : expected_(error_code{})
            {
            }

            explicit
            handler(error_code expected)
                : expected_(expected)
            {
            }

            explicit
            handler(boost::none_t)
            {
            }

            handler(handler&& other)
                : pass_(boost::exchange(other.pass_, true))
                , expected_(other.expected_)
            {
            }

            void operator()(error_code ec, std::size_t)
            {
                pass_ = true;
                if(expected_)
                    BEAST_EXPECTS(
                        ec == expected_, ec.message());
            }
        };

        auto const ep = net::ip::tcp::endpoint(
            net::ip::make_address("127.0.0.1"), 0);

        char buf[4];
        net::io_context ioc;
        auto mb = net::buffer(buf);
        std::memset(buf, 0, sizeof(buf));

        // cancel

        {
            test_server srv("", ep, log);
            stream_type s(ioc);
            s.connect(srv.local_endpoint());
            s.expires_never();
            s.socket().async_read_some(mb, handler(
                net::error::operation_aborted));
            s.cancel();
            ioc.run();
            ioc.restart();
        }

        // close

        {
            test_server srv("", ep, log);
            stream_type s(ioc);
            s.connect(srv.local_endpoint());
            s.expires_never();
            s.socket().async_read_some(mb,
                handler(boost::none));
            s.close();
            ioc.run();
            ioc.restart();
        }

        // destructor

        {
            test_server srv("", ep, log);
            {
                stream_type s(ioc);
                s.connect(srv.local_endpoint());
                s.expires_never();
                s.socket().async_read_some(mb,
                    handler(boost::none));
            }
            ioc.run();
            ioc.restart();
        }

        // customization points

        {
            stream_type s(ioc);
            beast::close_socket(s);
        }

        {
            error_code ec;
            stream_type s(ioc);
            teardown(role_type::client, s, ec);
        }

        {
            stream_type s(ioc);
            async_teardown(role_type::server, s,
                [](error_code)
                {
                });
        }
    }

    //--------------------------------------------------------------------------

    http::response<http::string_body>
    make_response(http::request<http::empty_body>)
    {
        return {};
    }

    void process_http_1 (tcp_stream& stream, net::yield_context yield)
    {
        flat_buffer buffer;
        http::request<http::empty_body> req;

        // Read the request, with a 15 second timeout
        stream.expires_after(std::chrono::seconds(15));
        http::async_read(stream, buffer, req, yield);

        // Calculate the response
        http::response<http::string_body> res = make_response(req);

        // Send the response, with a 30 second timeout.
        stream.expires_after (std::chrono::seconds(30));
        http::async_write (stream, res, yield);
    }

    void process_http_2 (tcp_stream& stream, net::yield_context yield)
    {
        flat_buffer buffer;
        http::request<http::empty_body> req;

        // Require that the read and write combined take no longer than 30 seconds
        stream.expires_after(std::chrono::seconds(30));

        http::async_read(stream, buffer, req, yield);

        http::response<http::string_body> res = make_response(req);
        http::async_write (stream, res, yield);
    }

    void
    testJavadocs()
    {
        BEAST_EXPECT(&basic_stream_test::process_http_1);
        BEAST_EXPECT(&basic_stream_test::process_http_2);
    }

    //--------------------------------------------------------------------------

    void
    testIssue1589()
    {
        net::io_context ioc;

        // the timer needlessly used polymorphic executor
        basic_stream<
            net::ip::tcp,
            net::io_context::executor_type>{ioc};

        // make sure strands work
        basic_stream<
            net::ip::tcp,
            net::strand<
                net::io_context::executor_type>>{
                    net::make_strand(ioc)};

        // address the problem in the issue
        {
            net::basic_stream_socket<
                net::ip::tcp,
                net::strand<
                    net::io_context::executor_type>
                        > sock(net::make_strand(ioc));
            basic_stream<
                net::ip::tcp,
                net::strand<
                    net::io_context::executor_type>,
                unlimited_rate_policy> stream(std::move(sock));
            BOOST_CORE_STATIC_ASSERT(
                std::is_convertible<
                    decltype(sock)::executor_type,
                    decltype(stream)::executor_type>::value);
        }
    }

    //--------------------------------------------------------------------------

#if BOOST_ASIO_HAS_CO_AWAIT
    void testAwaitableCompilation(
        basic_stream<net::ip::tcp>& stream,
        net::mutable_buffer outbuf,
        net::const_buffer inbuf,
        net::ip::tcp::resolver::results_type resolve_results)
    {
        static_assert(std::is_same_v<
            net::awaitable<std::size_t>, decltype(
                stream.async_read_some(outbuf, net::use_awaitable))>);

        static_assert(std::is_same_v<
            net::awaitable<std::size_t>, decltype(
                stream.async_write_some(inbuf, net::use_awaitable))>);

        static_assert(std::is_same_v<
            net::awaitable<net::ip::tcp::resolver::results_type::const_iterator>, decltype(
                stream.async_connect(
                    resolve_results.begin(),
                    resolve_results.end(),
                    net::use_awaitable))>);

        static_assert(std::is_same_v<
            net::awaitable<net::ip::tcp::endpoint>, decltype(
            stream.async_connect(
                resolve_results,
                net::use_awaitable))>);

        static_assert(std::is_same_v<
            net::awaitable<void>, decltype(
            stream.async_connect(
                resolve_results.begin()->endpoint(),
                net::use_awaitable))>);

        auto comparison_function = [](error_code const&, net::ip::tcp::endpoint) { return true; };

        static_assert(std::is_same_v<
            net::awaitable<net::ip::tcp::resolver::results_type::const_iterator>, decltype(
            stream.async_connect(
                resolve_results.begin(),
                resolve_results.end(),
                comparison_function,
                net::use_awaitable))>);

        static_assert(std::is_same_v<
            net::awaitable<net::ip::tcp::endpoint>, decltype(
            stream.async_connect(
                resolve_results,
                comparison_function,
                net::use_awaitable))>);
    }
#endif

    void
    testConnectionConditionArgs(
        basic_stream<net::ip::tcp> &stream,
        net::ip::tcp::resolver::results_type const &results)
    {
        struct condition
        {
            bool
            operator()(const error_code &,
                net::ip::tcp::endpoint const &) const;
        };

        {
            struct handler
            {
                void
                operator()(error_code const &,
                           net::ip::tcp::resolver::results_type::const_iterator) const;
            };

            static_assert(std::is_void<decltype(
            stream.async_connect(
                results.begin(),
                results.end(),
                condition(),
                handler()))>::value, "");
        }

        {
            struct handler
            {
                void
                operator()(error_code const &,
                           net::ip::tcp::endpoint const &);
            };

            static_assert(std::is_void<decltype(
            stream.async_connect(
                results,
                condition(),
                handler()))>::value, "");
        };
    }

    void
    testIssue2065()
    {
        using stream_type = basic_stream<tcp,
            net::io_context::executor_type>;

        char buf[4];
        net::io_context ioc;
        std::memset(buf, 0, sizeof(buf));
        net::mutable_buffer mb(buf, sizeof(buf));
        auto const ep = net::ip::tcp::endpoint(
            net::ip::make_address("127.0.0.1"), 0);

            // async_read_some

        {
            // success
            test_server srv("*", ep, log);
            stream_type s(ioc);
            s.socket().connect(srv.local_endpoint());
            s.expires_never();
            s.async_read_some(mb, handler({}, 1));
            s.async_read_some(net::buffer(buf, 0), handler({}, 0));
            ioc.run();
            ioc.restart();
        }
    }

    void
    run()
    {
        testSpecialMembers();
        testRead();
        testWrite();
        testConnect();
        testMembers();
        testJavadocs();
        testIssue1589();

#if BOOST_ASIO_HAS_CO_AWAIT
        // test for compilation success only
        boost::ignore_unused(&basic_stream_test::testAwaitableCompilation);
#endif
        boost::ignore_unused(&basic_stream_test::testConnectionConditionArgs);
        testIssue2065();
    }
};

BEAST_DEFINE_TESTSUITE(beast,core,basic_stream);

} // beast
} // boost
