//
// 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/http/write.hpp>

#include <boost/beast/http/empty_body.hpp>
#include <boost/beast/http/fields.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/core/buffers_to_string.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/multi_buffer.hpp>
#include <boost/beast/core/static_string.hpp>
#include <boost/beast/_experimental/test/stream.hpp>
#include <boost/beast/test/yield_to.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/connect_pipe.hpp>
#include <boost/asio/readable_pipe.hpp>
#include <boost/asio/writable_pipe.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/strand.hpp>
#include <sstream>
#include <string>
#if BOOST_ASIO_HAS_CO_AWAIT
#include <boost/asio/use_awaitable.hpp>
#endif

namespace boost {
namespace beast {
namespace http {

class write_test
    : public beast::unit_test::suite
    , public test::enable_yield_to
{
public:
    struct unsized_body
    {
        using value_type = std::string;

        class writer
        {
            value_type const& body_;

        public:
            using const_buffers_type =
                net::const_buffer;

            template<bool isRequest, class Fields>
            writer(
                header<isRequest, Fields> const&,
                value_type const& b)
                : body_(b)
            {
            }

            void
            init(error_code& ec)
            {
                ec = {};
            }

            boost::optional<std::pair<const_buffers_type, bool>>
            get(error_code& ec)
            {
                ec = {};
                return {{const_buffers_type{
                    body_.data(), body_.size()}, false}};
            }
        };
    };

    template<
        bool isSplit,
        bool isFinalEmpty
    >
    struct test_body
    {
        struct value_type
        {
            std::string s;
            bool mutable read = false;
        };

        class writer
        {
            int step_ = 0;
            value_type const& body_;

        public:
            using const_buffers_type =
                net::const_buffer;

            template<bool isRequest, class Fields>
            writer(
                header<isRequest, Fields> const&,
                value_type const& b)
                : body_(b)
            {
            }

            void
            init(error_code& ec)
            {
                ec = {};
            }

            boost::optional<std::pair<const_buffers_type, bool>>
            get(error_code& ec)
            {
                ec = {};
                body_.read = true;
                return get(
                    std::integral_constant<bool, isSplit>{},
                    std::integral_constant<bool, isFinalEmpty>{});
            }

        private:
            boost::optional<std::pair<const_buffers_type, bool>>
            get(
                std::false_type,    // isSplit
                std::false_type)    // isFinalEmpty
            {
                if(body_.s.empty())
                    return boost::none;
                return {{net::buffer(
                    body_.s.data(), body_.s.size()), false}};
            }

            boost::optional<std::pair<const_buffers_type, bool>>
            get(
                std::false_type,    // isSplit
                std::true_type)     // isFinalEmpty
            {
                if(body_.s.empty())
                    return boost::none;
                switch(step_)
                {
                case 0:
                    step_ = 1;
                    return {{net::buffer(
                        body_.s.data(), body_.s.size()), true}};
                default:
                    return boost::none;
                }
            }

            boost::optional<std::pair<const_buffers_type, bool>>
            get(
                std::true_type,     // isSplit
                std::false_type)    // isFinalEmpty
            {
                auto const n = (body_.s.size() + 1) / 2;
                switch(step_)
                {
                case 0:
                    if(n == 0)
                        return boost::none;
                    step_ = 1;
                    return {{net::buffer(body_.s.data(), n),
                        body_.s.size() > 1}};
                default:
                    return {{net::buffer(body_.s.data() + n,
                        body_.s.size() - n), false}};
                }
            }

            boost::optional<std::pair<const_buffers_type, bool>>
            get(
                std::true_type,     // isSplit
                std::true_type)     // isFinalEmpty
            {
                auto const n = (body_.s.size() + 1) / 2;
                switch(step_)
                {
                case 0:
                    if(n == 0)
                        return boost::none;
                    step_ = body_.s.size() > 1 ? 1 : 2;
                    return {{net::buffer(body_.s.data(), n), true}};
                case 1:
                    BOOST_ASSERT(body_.s.size() > 1);
                    step_ = 2;
                    return {{net::buffer(body_.s.data() + n,
                        body_.s.size() - n), true}};
                default:
                    return boost::none;
                }
            }
        };
    };

    struct fail_body
    {
        class writer;

        class value_type
        {
            friend class writer;

            std::string s_;
            test::fail_count& fc_;

        public:
            explicit
            value_type(test::fail_count& fc)
                : fc_(fc)
            {
            }

            value_type&
            operator=(std::string s)
            {
                s_ = std::move(s);
                return *this;
            }
        };

        class writer
        {
            std::size_t n_ = 0;
            value_type const& body_;

        public:
            using const_buffers_type =
                net::const_buffer;

            template<bool isRequest, class Fields>
            explicit
            writer(header<isRequest, Fields> const&, value_type const& b)
                : body_(b)
            {
            }

            void
            init(error_code& ec)
            {
                body_.fc_.fail(ec);
            }

            boost::optional<std::pair<const_buffers_type, bool>>
            get(error_code& ec)
            {
                if(body_.fc_.fail(ec))
                    return boost::none;
                if(n_ >= body_.s_.size())
                    return boost::none;
                return {{const_buffers_type{
                    body_.s_.data() + n_++, 1}, true}};
            }
        };
    };

    template<bool isRequest, class Body, class Fields>
    static
    std::string
    to_string(message<isRequest, Body, Fields> const& m)
    {
        std::stringstream ss;
        ss << m;
        return ss.str();
    }

    template<bool isRequest>
    bool
    equal_body(string_view sv, string_view body)
    {
        test::stream ts{ioc_, sv}, tr{ioc_};
        ts.connect(tr);
        message<isRequest, string_body, fields> m;
        multi_buffer b;
        ts.close_remote();
        try
        {
            read(ts, b, m);
            return m.body() == body;
        }
        catch(std::exception const& e)
        {
            log << "equal_body: " << e.what() << std::endl;
            return false;
        }
    }

    template<bool isRequest, class Body, class Fields>
    std::string
    str(message<isRequest, Body, Fields> const& m)
    {
        test::stream ts{ioc_}, tr{ioc_};
        ts.connect(tr);
        error_code ec;
        write(ts, m, ec);
        if(ec && ec != error::end_of_stream)
            BOOST_THROW_EXCEPTION(system_error{ec});
        return std::string(tr.str());
    }

    void
    testAsyncWrite(yield_context do_yield)
    {
        {
            response<string_body> m;
            m.version(10);
            m.result(status::ok);
            m.set(field::server, "test");
            m.set(field::content_length, "5");
            m.body() = "*****";
            error_code ec;
            test::stream ts{ioc_}, tr{ioc_};
            ts.connect(tr);
            async_write(ts, m, do_yield[ec]);
            BEAST_EXPECT(! m.keep_alive());
            if(BEAST_EXPECTS(! ec, ec.message()))
                BEAST_EXPECT(tr.str() ==
                    "HTTP/1.0 200 OK\r\n"
                    "Server: test\r\n"
                    "Content-Length: 5\r\n"
                    "\r\n"
                    "*****");
        }
        {
            response<string_body> m;
            m.version(11);
            m.result(status::ok);
            m.set(field::server, "test");
            m.set(field::transfer_encoding, "chunked");
            m.body() = "*****";
            error_code ec;
            test::stream ts{ioc_}, tr{ioc_};
            ts.connect(tr);
            async_write(ts, m, do_yield[ec]);
            if(BEAST_EXPECTS(! ec, ec.message()))
                BEAST_EXPECT(tr.str() ==
                    "HTTP/1.1 200 OK\r\n"
                    "Server: test\r\n"
                    "Transfer-Encoding: chunked\r\n"
                    "\r\n"
                    "5\r\n"
                    "*****\r\n"
                    "0\r\n\r\n");
        }
    }

    void
    testFailures(yield_context do_yield)
    {
        static std::size_t constexpr limit = 100;
        std::size_t n;

        for(n = 0; n < limit; ++n)
        {
            test::fail_count fc(n);
            test::stream ts{ioc_, fc}, tr{ioc_};
            ts.connect(tr);
            request<fail_body> m(verb::get, "/", 10, fc);
            m.set(field::user_agent, "test");
            m.set(field::connection, "keep-alive");
            m.set(field::content_length, "5");
            m.body() = "*****";
            try
            {
                write(ts, m);
                BEAST_EXPECT(tr.str() ==
                    "GET / HTTP/1.0\r\n"
                    "User-Agent: test\r\n"
                    "Connection: keep-alive\r\n"
                    "Content-Length: 5\r\n"
                    "\r\n"
                    "*****"
                );
                pass();
                break;
            }
            catch(std::exception const&)
            {
            }
        }
        BEAST_EXPECT(n < limit);

        for(n = 0; n < limit; ++n)
        {
            test::fail_count fc(n);
            test::stream ts{ioc_, fc}, tr{ioc_};
            ts.connect(tr);
            request<fail_body> m{verb::get, "/", 10, fc};
            m.set(field::user_agent, "test");
            m.set(field::transfer_encoding, "chunked");
            m.body() = "*****";
            error_code ec = test::error::test_failure;
            write(ts, m, ec);
            if(! ec)
            {
                BEAST_EXPECT(! m.keep_alive());
                BEAST_EXPECT(tr.str() ==
                    "GET / HTTP/1.0\r\n"
                    "User-Agent: test\r\n"
                    "Transfer-Encoding: chunked\r\n"
                    "\r\n"
                    "1\r\n*\r\n"
                    "1\r\n*\r\n"
                    "1\r\n*\r\n"
                    "1\r\n*\r\n"
                    "1\r\n*\r\n"
                    "0\r\n\r\n"
                );
                break;
            }
        }
        BEAST_EXPECT(n < limit);

        for(n = 0; n < limit; ++n)
        {
            test::fail_count fc(n);
            test::stream ts{ioc_, fc}, tr{ioc_};
            ts.connect(tr);
            request<fail_body> m{verb::get, "/", 10, fc};
            m.set(field::user_agent, "test");
            m.set(field::transfer_encoding, "chunked");
            m.body() = "*****";
            error_code ec = test::error::test_failure;
            async_write(ts, m, do_yield[ec]);
            if(! ec)
            {
                BEAST_EXPECT(! m.keep_alive());
                BEAST_EXPECT(tr.str() ==
                    "GET / HTTP/1.0\r\n"
                    "User-Agent: test\r\n"
                    "Transfer-Encoding: chunked\r\n"
                    "\r\n"
                    "1\r\n*\r\n"
                    "1\r\n*\r\n"
                    "1\r\n*\r\n"
                    "1\r\n*\r\n"
                    "1\r\n*\r\n"
                    "0\r\n\r\n"
                );
                break;
            }
        }
        BEAST_EXPECT(n < limit);

        for(n = 0; n < limit; ++n)
        {
            test::fail_count fc(n);
            test::stream ts{ioc_, fc}, tr{ioc_};
            ts.connect(tr);
            request<fail_body> m{verb::get, "/", 10, fc};
            m.set(field::user_agent, "test");
            m.set(field::connection, "keep-alive");
            m.set(field::content_length, "5");
            m.body() = "*****";
            error_code ec = test::error::test_failure;
            write(ts, m, ec);
            if(! ec)
            {
                BEAST_EXPECT(tr.str() ==
                    "GET / HTTP/1.0\r\n"
                    "User-Agent: test\r\n"
                    "Connection: keep-alive\r\n"
                    "Content-Length: 5\r\n"
                    "\r\n"
                    "*****"
                );
                break;
            }
        }
        BEAST_EXPECT(n < limit);

        for(n = 0; n < limit; ++n)
        {
            test::fail_count fc(n);
            test::stream ts{ioc_, fc}, tr{ioc_};
            ts.connect(tr);
            request<fail_body> m{verb::get, "/", 10, fc};
            m.set(field::user_agent, "test");
            m.set(field::connection, "keep-alive");
            m.set(field::content_length, "5");
            m.body() = "*****";
            error_code ec = test::error::test_failure;
            async_write(ts, m, do_yield[ec]);
            if(! ec)
            {
                BEAST_EXPECT(tr.str() ==
                    "GET / HTTP/1.0\r\n"
                    "User-Agent: test\r\n"
                    "Connection: keep-alive\r\n"
                    "Content-Length: 5\r\n"
                    "\r\n"
                    "*****"
                );
                break;
            }
        }
        BEAST_EXPECT(n < limit);
    }

    void
    testOutput()
    {
        // auto content-length HTTP/1.0
        {
            request<string_body> m;
            m.method(verb::get);
            m.target("/");
            m.version(10);
            m.set(field::user_agent, "test");
            m.body() = "*";
            m.prepare_payload();
            BEAST_EXPECT(str(m) ==
                "GET / HTTP/1.0\r\n"
                "User-Agent: test\r\n"
                "Content-Length: 1\r\n"
                "\r\n"
                "*"
            );
        }
        // no content-length HTTP/1.0
        {
            request<unsized_body> m;
            m.method(verb::get);
            m.target("/");
            m.version(10);
            m.set(field::user_agent, "test");
            m.body() = "*";
            m.prepare_payload();
            test::stream ts{ioc_}, tr{ioc_};
            ts.connect(tr);
            error_code ec;
            write(ts, m, ec);
            BEAST_EXPECT(! m.keep_alive());
            BEAST_EXPECTS(! ec, ec.message());
            BEAST_EXPECT(tr.str() ==
                "GET / HTTP/1.0\r\n"
                "User-Agent: test\r\n"
                "\r\n"
                "*"
            );
        }
        // auto content-length HTTP/1.1
        {
            request<string_body> m;
            m.method(verb::get);
            m.target("/");
            m.version(11);
            m.set(field::user_agent, "test");
            m.body() = "*";
            m.prepare_payload();
            BEAST_EXPECT(str(m) ==
                "GET / HTTP/1.1\r\n"
                "User-Agent: test\r\n"
                "Content-Length: 1\r\n"
                "\r\n"
                "*"
            );
        }
        // no content-length HTTP/1.1
        {
            request<unsized_body> m;
            m.method(verb::get);
            m.target("/");
            m.version(11);
            m.set(field::user_agent, "test");
            m.body() = "*";
            m.prepare_payload();
            test::stream ts{ioc_}, tr{ioc_};
            ts.connect(tr);
            error_code ec;
            write(ts, m, ec);
            BEAST_EXPECT(tr.str() ==
                "GET / HTTP/1.1\r\n"
                "User-Agent: test\r\n"
                "Transfer-Encoding: chunked\r\n"
                "\r\n"
                "1\r\n"
                "*\r\n"
                "0\r\n\r\n"
            );
        }
    }

    void test_std_ostream()
    {
        // Conversion to std::string via operator<<
        {
            request<string_body> m;
            m.method(verb::get);
            m.target("/");
            m.version(11);
            m.set(field::user_agent, "test");
            m.body() = "*";
            BEAST_EXPECT(to_string(m) ==
                "GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n*");
        }

        // Output to std::ostream
        {
            request<string_body> m{verb::get, "/", 11};
            std::stringstream ss;
            ss << m;
            BEAST_EXPECT(ss.str() ==
                "GET / HTTP/1.1\r\n"
                "\r\n");
        }

        // Output header to std::ostream
        {
            request<string_body> m{verb::get, "/", 11};
            std::stringstream ss;
            ss << m.base();
            BEAST_EXPECT(ss.str() ==
                "GET / HTTP/1.1\r\n"
                "\r\n");
        }
    }

    // Ensure completion handlers are not leaked
    struct handler
    {
        static std::atomic<std::size_t>&
        count() { static std::atomic<std::size_t> n; return n; }
        handler() { ++count(); }
        ~handler() { --count(); }
        handler(handler const&) { ++count(); }
        void operator()(error_code const&, std::size_t) const {}
    };

    void
    testIoService()
    {
        {
            // Make sure handlers are not destroyed
            // after calling io_context::stop
            net::io_context ioc;
            test::stream ts{ioc};
            BEAST_EXPECT(handler::count() == 0);
            request<string_body> m;
            m.method(verb::get);
            m.version(11);
            m.target("/");
            m.set("Content-Length", "5");
            m.body() = "*****";
            async_write(ts, m, handler{});
            BEAST_EXPECT(handler::count() > 0);
            ioc.stop();
            BEAST_EXPECT(handler::count() > 0);
            ioc.restart();
            BEAST_EXPECT(handler::count() > 0);
            ioc.run_one();
            BEAST_EXPECT(handler::count() == 0);
        }
        {
            // Make sure uninvoked handlers are
            // destroyed when calling ~io_context
            {
                net::io_context ioc;
                test::stream ts{ioc}, tr{ioc};
                ts.connect(tr);
                BEAST_EXPECT(handler::count() == 0);
                request<string_body> m;
                m.method(verb::get);
                m.version(11);
                m.target("/");
                m.set("Content-Length", "5");
                m.body() = "*****";
                async_write(ts, m, handler{});
                BEAST_EXPECT(handler::count() > 0);
            }
            BEAST_EXPECT(handler::count() == 0);
        }
    }

    template<
        class Stream,
        bool isRequest, class Body, class Fields>
    void
    do_write(
        Stream& stream,
        message<isRequest, Body, Fields> const& m,
        error_code& ec)
    {
        serializer<isRequest, Body, Fields> sr{m};
        for(;;)
        {
            write_some(stream, sr, ec);
            if(ec)
                return;
            if(sr.is_done())
                break;
        }
    }

    template<
        class Stream,
        bool isRequest, class Body, class Fields>
    void
    do_async_write(
        Stream& stream,
        message<isRequest, Body, Fields> const& m,
        error_code& ec,
        yield_context yield)
    {
        serializer<isRequest, Body, Fields> sr{m};
        for(;;)
        {
            async_write_some(stream, sr, yield[ec]);
            if(ec)
                return;
            if(sr.is_done())
                break;
        }
    }

    template<class Body>
    void
    testWriteStream(net::yield_context yield)
    {
        test::stream ts{ioc_}, tr{ioc_};
        ts.connect(tr);
        ts.write_size(3);

        response<Body> m0;
        m0.version(11);
        m0.result(status::ok);
        m0.reason("OK");
        m0.set(field::server, "test");
        m0.body().s = "Hello, world!\n";

        {
            std::string const result =
                "HTTP/1.1 200 OK\r\n"
                "Server: test\r\n"
                "\r\n"
                "Hello, world!\n";
            {
                auto m = m0;
                error_code ec;
                do_write(ts, m, ec);
                BEAST_EXPECT(tr.str() == result);
                BEAST_EXPECT(equal_body<false>(
                    tr.str(), m.body().s));
                tr.clear();
            }
            {
                auto m = m0;
                error_code ec;
                do_async_write(ts, m, ec, yield);
                BEAST_EXPECT(tr.str() == result);
                BEAST_EXPECT(equal_body<false>(
                    tr.str(), m.body().s));
                tr.clear();
            }
            {
                auto m = m0;
                response_serializer<Body, fields> sr{m};
                sr.split(true);
                for(;;)
                {
                    write_some(ts, sr);
                    if(sr.is_header_done())
                        break;
                }
                BEAST_EXPECT(! m.body().read);
                tr.clear();
            }
            {
                auto m = m0;
                response_serializer<Body, fields> sr{m};
                sr.split(true);
                for(;;)
                {
                    async_write_some(ts, sr, yield);
                    if(sr.is_header_done())
                        break;
                }
                BEAST_EXPECT(! m.body().read);
                tr.clear();
            }
        }
        {
            m0.set("Transfer-Encoding", "chunked");
            {
                auto m = m0;
                error_code ec;
                do_write(ts, m, ec);
                BEAST_EXPECT(equal_body<false>(
                    tr.str(), m.body().s));
                tr.clear();
            }
            {
                auto m = m0;
                error_code ec;
                do_async_write(ts, m, ec, yield);
                BEAST_EXPECT(equal_body<false>(
                    tr.str(), m.body().s));
                tr.clear();
            }
            {
                auto m = m0;
                response_serializer<Body, fields> sr{m};
                sr.split(true);
                for(;;)
                {
                    write_some(ts, sr);
                    if(sr.is_header_done())
                        break;
                }
                BEAST_EXPECT(! m.body().read);
                tr.clear();
            }
            {
                auto m = m0;
                response_serializer<Body, fields> sr{m};
                sr.split(true);
                for(;;)
                {
                    async_write_some(ts, sr, yield);
                    if(sr.is_header_done())
                        break;
                }
                BEAST_EXPECT(! m.body().read);
                tr.clear();
            }
        }
    }

    void
    testIssue655()
    {
        net::io_context ioc;
        test::stream ts{ioc}, tr{ioc};
        ts.connect(tr);
        response<empty_body> res;
        res.chunked(true);
        response_serializer<empty_body> sr{res};
        async_write_header(ts, sr,
            [&](error_code const&, std::size_t)
            {
            });
        ioc.run();
    }

    struct copyable_handler
    {
        template<class... Args>
        void
        operator()(Args&&...) const
        {
        }
    };

    void
    testAsioHandlerInvoke()
    {
        using strand = net::strand<
            net::io_context::executor_type>;

        // make sure things compile, also can set a
        // breakpoint in asio_handler_invoke to make sure
        // it is instantiated.
        {
            net::io_context ioc;
            strand s{ioc.get_executor()};
            test::stream ts{ioc};
            flat_buffer b;
            request<empty_body> m;
            request_serializer<empty_body, fields> sr{m};
            async_write_some(ts, sr,
                net::bind_executor(
                    s, copyable_handler{}));
        }
        {
            net::io_context ioc;
            strand s{ioc.get_executor()};
            test::stream ts{ioc};
            flat_buffer b;
            request<empty_body> m;
            request_serializer<empty_body, fields> sr{m};
            async_write(ts, sr,
                net::bind_executor(
                    s, copyable_handler{}));
        }
        {
            net::io_context ioc;
            strand s{ioc.get_executor()};
            test::stream ts{ioc};
            flat_buffer b;
            request<empty_body> m;
            async_write(ts, m,
                net::bind_executor(
                    s, copyable_handler{}));
        }
    }

    struct const_body_writer
    {
        struct value_type{};

        struct writer
        {
            using const_buffers_type =
                net::const_buffer;

            template<bool isRequest, class Fields>
            writer(
                header<isRequest, Fields> const&,
                value_type const&)
            {
            }

            void
            init(error_code& ec)
            {
                ec = {};
            }

            boost::optional<std::pair<const_buffers_type, bool>>
            get(error_code& ec)
            {
                ec = {};
                return {{const_buffers_type{"", 0}, false}};
            }
        };
    };

    struct mutable_body_writer
    {
        struct value_type{};

        struct writer
        {
            using const_buffers_type =
                net::const_buffer;

            template<bool isRequest, class Fields>
            writer(
                header<isRequest, Fields>&,
                value_type&)
            {
            }

            void
            init(error_code& ec)
            {
                ec = {};
            }

            boost::optional<std::pair<const_buffers_type, bool>>
            get(error_code& ec)
            {
                ec = {};
                return {{const_buffers_type{"", 0}, false}};
            }
        };
    };

    void
    testBodyWriters()
    {
        {
            test::stream s{ioc_};
            message<true, const_body_writer> m;
            try
            {
                write(s, m);
            }
            catch(std::exception const&)
            {
            }
        }
        {
            error_code ec;
            test::stream s{ioc_};
            message<true, const_body_writer> m;
            write(s, m, ec);
        }
        {
            test::stream s{ioc_};
            message<true, mutable_body_writer> m;
            try
            {
                write(s, m);
            }
            catch(std::exception const&)
            {
            }
        }
        {
            error_code ec;
            test::stream s{ioc_};
            message<true, mutable_body_writer> m;
            write(s, m, ec);
        }
    }

#if BOOST_ASIO_HAS_CO_AWAIT
    void testAwaitableCompiles(
        test::stream& stream,
        serializer<true, string_body>& request_serializer,
        request<string_body>& req,
        request<string_body> const& creq,
        serializer<false, string_body>& response_serializer,
        response<string_body>& resp,
        response<string_body> const& cresp)
    {
        static_assert(std::is_same_v<
            net::awaitable<std::size_t>, decltype(
            http::async_write(stream, request_serializer, net::use_awaitable))>);

        static_assert(std::is_same_v<
            net::awaitable<std::size_t>, decltype(
            http::async_write(stream, response_serializer, net::use_awaitable))>);

        static_assert(std::is_same_v<
            net::awaitable<std::size_t>, decltype(
            http::async_write(stream, req, net::use_awaitable))>);

        static_assert(std::is_same_v<
            net::awaitable<std::size_t>, decltype(
            http::async_write(stream, creq, net::use_awaitable))>);

        static_assert(std::is_same_v<
            net::awaitable<std::size_t>, decltype(
            http::async_write(stream, resp, net::use_awaitable))>);

        static_assert(std::is_same_v<
            net::awaitable<std::size_t>, decltype(
            http::async_write(stream, cresp, net::use_awaitable))>);

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

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

        static_assert(std::is_same_v<
            net::awaitable<std::size_t>, decltype(
            http::async_write_header(stream, request_serializer, net::use_awaitable))>);

        static_assert(std::is_same_v<
            net::awaitable<std::size_t>, decltype(
            http::async_write_header(stream, response_serializer, net::use_awaitable))>);
    }
#endif

    void
    testCancellation(yield_context do_yield)
    {
        // this is tested on a pipe
        // because the test::stream doesn't implement cancellation 
        {
            response<string_body> m;
            m.version(10);
            m.result(status::ok);
            m.set(field::server, "test");
            m.set(field::content_length, "5");
            // make the content big enough so it overflows the buffer 
            // that'll make the op never complete if we don't cancel
            m.body().assign(10000000, '*');
            error_code ec;
            net::writable_pipe ts{ioc_};
            net::readable_pipe tr{ioc_};
            net::connect_pipe(tr, ts);
            net::cancellation_signal cl;
            net::post(ioc_, [&]{cl.emit(net::cancellation_type::all);});
            net::steady_timer timeout(ioc_, std::chrono::seconds(5));
            timeout.async_wait(
                [&](error_code ec)
                {
                    BEAST_EXPECT(ec == net::error::operation_aborted);
                    if (!ec) // this means the cancel failed!
                        ts.close();
                });

            async_write(ts, m, net::bind_cancellation_slot(cl.slot(), do_yield[ec]));
            timeout.cancel();
            net::post(ioc_, do_yield); // wait for the timeout to finish
            BEAST_EXPECT(ec == net::error::operation_aborted);
        }
        {
            response<string_body> m;
            m.version(11);
            m.result(status::ok);
            m.set(field::server, "test");
            m.set(field::transfer_encoding, "chunked");
            m.body().assign(10000000, '*');
            error_code ec;
            net::writable_pipe ts{ioc_};
            net::readable_pipe tr{ioc_};
            net::connect_pipe(tr, ts);
            net::cancellation_signal cl;
            net::post(ioc_, [&]{cl.emit(net::cancellation_type::all);});
            net::steady_timer timeout(ioc_, std::chrono::seconds(5));
            timeout.async_wait(
                [&](error_code ec)
                {
                    BEAST_EXPECT(ec == net::error::operation_aborted);
                    if (!ec) // this means the cancel failed!
                        ts.close();
                });
            async_write(ts, m, net::bind_cancellation_slot(cl.slot(), do_yield[ec]));
            timeout.cancel();
            net::post(ioc_, do_yield); // wait for the timeout to finish
            BEAST_EXPECT(ec == net::error::operation_aborted);
        }
        // the timer handler may be invoked after the test suite is complete if we don't post.
        asio::post(ioc_, do_yield);
    }

    void
    run() override
    {
        testIssue655();
        yield_to(
            [&](yield_context yield)
            {
                testAsyncWrite(yield);
                testFailures(yield);
            });
        testOutput();
        test_std_ostream();
        testIoService();
        yield_to(
            [&](yield_context yield)
            {
                testWriteStream<test_body<false, false>>(yield);
                testWriteStream<test_body<false,  true>>(yield);
                testWriteStream<test_body< true, false>>(yield);
                testWriteStream<test_body< true,  true>>(yield);
            });
        testAsioHandlerInvoke();
        testBodyWriters();
#if BOOST_ASIO_HAS_CO_AWAIT
        boost::ignore_unused(&write_test::testAwaitableCompiles);
#endif
        yield_to(
            [&](yield_context yield)
            {
                testCancellation(yield);
            });
    }
};

BEAST_DEFINE_TESTSUITE(beast,http,write);

} // http
} // beast
} // boost
