1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
 
3 +
// Copyright (c) 2026 Steve Gerbino
3  
//
4  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
7  
//
7  
// Official repository: https://github.com/cppalliance/corosio
8  
// Official repository: https://github.com/cppalliance/corosio
8  
//
9  
//
9  

10  

10  
#ifndef BOOST_COROSIO_TEST_MOCKET_HPP
11  
#ifndef BOOST_COROSIO_TEST_MOCKET_HPP
11  
#define BOOST_COROSIO_TEST_MOCKET_HPP
12  
#define BOOST_COROSIO_TEST_MOCKET_HPP
12  

13  

13 -
#include <boost/corosio/detail/config.hpp>
14 +
#include <boost/corosio/detail/except.hpp>
 
15 +
#include <boost/corosio/io_context.hpp>
 
16 +
#include <boost/corosio/tcp_acceptor.hpp>
14  
#include <boost/corosio/tcp_socket.hpp>
17  
#include <boost/corosio/tcp_socket.hpp>
15  
#include <boost/capy/buffers/buffer_copy.hpp>
18  
#include <boost/capy/buffers/buffer_copy.hpp>
16  
#include <boost/capy/buffers/make_buffer.hpp>
19  
#include <boost/capy/buffers/make_buffer.hpp>
17  
#include <boost/capy/error.hpp>
20  
#include <boost/capy/error.hpp>
 
21 +
#include <boost/capy/ex/run_async.hpp>
18  
#include <boost/capy/io_result.hpp>
22  
#include <boost/capy/io_result.hpp>
 
23 +
#include <boost/capy/task.hpp>
19 -
#include <system_error>
 
20  
#include <boost/capy/test/fuse.hpp>
24  
#include <boost/capy/test/fuse.hpp>
21  

25  

22  
#include <cstddef>
26  
#include <cstddef>
23 -
#include <new>
27 +
#include <cstdio>
 
28 +
#include <cstring>
 
29 +
#include <stdexcept>
24  
#include <string>
30  
#include <string>
 
31 +
#include <system_error>
25  
#include <utility>
32  
#include <utility>
26 -
namespace boost::capy {
 
27 -
class execution_context;
 
28 -
} // namespace boost::capy
 
29 -

 
30  

33  

31  
namespace boost::corosio::test {
34  
namespace boost::corosio::test {
32  

35  

33  
/** A mock socket for testing I/O operations.
36  
/** A mock socket for testing I/O operations.
34  

37  

35  
    This class provides a testable socket-like interface where data
38  
    This class provides a testable socket-like interface where data
36  
    can be staged for reading and expected data can be validated on
39  
    can be staged for reading and expected data can be validated on
37 -
    writes. A mocket is paired with a regular tcp_socket using
40 +
    writes. A mocket is paired with a regular socket using
38  
    @ref make_mocket_pair, allowing bidirectional communication testing.
41  
    @ref make_mocket_pair, allowing bidirectional communication testing.
39  

42  

40  
    When reading, data comes from the `provide()` buffer first.
43  
    When reading, data comes from the `provide()` buffer first.
41  
    When writing, data is validated against the `expect()` buffer.
44  
    When writing, data is validated against the `expect()` buffer.
42  
    Once buffers are exhausted, I/O passes through to the underlying
45  
    Once buffers are exhausted, I/O passes through to the underlying
43  
    socket connection.
46  
    socket connection.
44  

47  

45  
    Satisfies the `capy::Stream` concept.
48  
    Satisfies the `capy::Stream` concept.
46  

49  

 
50 +
    @tparam Socket The underlying socket type (default `tcp_socket`).
 
51 +

47  
    @par Thread Safety
52  
    @par Thread Safety
48  
    Not thread-safe. All operations must occur on a single thread.
53  
    Not thread-safe. All operations must occur on a single thread.
49  
    All coroutines using the mocket must be suspended when calling
54  
    All coroutines using the mocket must be suspended when calling
50  
    `expect()` or `provide()`.
55  
    `expect()` or `provide()`.
51  

56  

52  
    @see make_mocket_pair
57  
    @see make_mocket_pair
53  
*/
58  
*/
54 -
class BOOST_COROSIO_DECL mocket
59 +
template<class Socket = tcp_socket>
 
60 +
class basic_mocket
55  
{
61  
{
56 -
    tcp_socket sock_;
62 +
    Socket sock_;
57  
    std::string provide_;
63  
    std::string provide_;
58  
    std::string expect_;
64  
    std::string expect_;
59  
    capy::test::fuse fuse_;
65  
    capy::test::fuse fuse_;
60  
    std::size_t max_read_size_;
66  
    std::size_t max_read_size_;
61  
    std::size_t max_write_size_;
67  
    std::size_t max_write_size_;
62  

68  

63  
    template<class MutableBufferSequence>
69  
    template<class MutableBufferSequence>
64  
    std::size_t consume_provide(MutableBufferSequence const& buffers) noexcept;
70  
    std::size_t consume_provide(MutableBufferSequence const& buffers) noexcept;
65  

71  

66  
    template<class ConstBufferSequence>
72  
    template<class ConstBufferSequence>
67  
    bool validate_expect(
73  
    bool validate_expect(
68  
        ConstBufferSequence const& buffers, std::size_t& bytes_written);
74  
        ConstBufferSequence const& buffers, std::size_t& bytes_written);
69  

75  

70  
public:
76  
public:
71  
    template<class MutableBufferSequence>
77  
    template<class MutableBufferSequence>
72  
    class read_some_awaitable;
78  
    class read_some_awaitable;
73  

79  

74  
    template<class ConstBufferSequence>
80  
    template<class ConstBufferSequence>
75  
    class write_some_awaitable;
81  
    class write_some_awaitable;
76  

82  

77  
    /** Destructor.
83  
    /** Destructor.
78  
    */
84  
    */
79 -
    ~mocket();
85 +
    ~basic_mocket() = default;
80  

86  

81  
    /** Construct a mocket.
87  
    /** Construct a mocket.
82  

88  

83  
        @param ctx The execution context for the socket.
89  
        @param ctx The execution context for the socket.
84  
        @param f The fuse for error injection testing.
90  
        @param f The fuse for error injection testing.
85  
        @param max_read_size Maximum bytes per read operation.
91  
        @param max_read_size Maximum bytes per read operation.
86  
        @param max_write_size Maximum bytes per write operation.
92  
        @param max_write_size Maximum bytes per write operation.
87  
    */
93  
    */
88 -
    mocket(
94 +
    basic_mocket(
89  
        capy::execution_context& ctx,
95  
        capy::execution_context& ctx,
90 -
        capy::test::fuse f = {},
96 +
        capy::test::fuse f         = {},
91 -
        std::size_t max_read_size = std::size_t(-1),
97 +
        std::size_t max_read_size  = std::size_t(-1),
92 -
        std::size_t max_write_size = std::size_t(-1));
98 +
        std::size_t max_write_size = std::size_t(-1))
 
99 +
        : sock_(ctx)
 
100 +
        , fuse_(std::move(f))
 
101 +
        , max_read_size_(max_read_size)
 
102 +
        , max_write_size_(max_write_size)
 
103 +
    {
 
104 +
        if (max_read_size == 0)
 
105 +
            detail::throw_logic_error("mocket: max_read_size cannot be 0");
 
106 +
        if (max_write_size == 0)
 
107 +
            detail::throw_logic_error("mocket: max_write_size cannot be 0");
 
108 +
    }
93  

109  

94  
    /** Move constructor.
110  
    /** Move constructor.
95  
    */
111  
    */
96 -
    mocket(mocket&& other) noexcept;
112 +
    basic_mocket(basic_mocket&& other) noexcept
 
113 +
        : sock_(std::move(other.sock_))
 
114 +
        , provide_(std::move(other.provide_))
 
115 +
        , expect_(std::move(other.expect_))
 
116 +
        , fuse_(std::move(other.fuse_))
 
117 +
        , max_read_size_(other.max_read_size_)
 
118 +
        , max_write_size_(other.max_write_size_)
 
119 +
    {
 
120 +
    }
97  

121  

98  
    /** Move assignment.
122  
    /** Move assignment.
99  
    */
123  
    */
100 -
    mocket& operator=(mocket&& other) noexcept;
124 +
    basic_mocket& operator=(basic_mocket&& other) noexcept
 
125 +
    {
 
126 +
        if (this != &other)
 
127 +
        {
 
128 +
            sock_           = std::move(other.sock_);
 
129 +
            provide_        = std::move(other.provide_);
 
130 +
            expect_         = std::move(other.expect_);
 
131 +
            fuse_           = other.fuse_;
 
132 +
            max_read_size_  = other.max_read_size_;
 
133 +
            max_write_size_ = other.max_write_size_;
 
134 +
        }
 
135 +
        return *this;
 
136 +
    }
101  

137  

102 -
    mocket(mocket const&) = delete;
138 +
    basic_mocket(basic_mocket const&)            = delete;
103 -
    mocket& operator=(mocket const&) = delete;
139 +
    basic_mocket& operator=(basic_mocket const&) = delete;
104  

140  

105  
    /** Return the execution context.
141  
    /** Return the execution context.
106  

142  

107  
        @return Reference to the execution context that owns this mocket.
143  
        @return Reference to the execution context that owns this mocket.
108  
    */
144  
    */
109  
    capy::execution_context& context() const noexcept
145  
    capy::execution_context& context() const noexcept
110  
    {
146  
    {
111  
        return sock_.context();
147  
        return sock_.context();
112  
    }
148  
    }
113  

149  

114  
    /** Return the underlying socket.
150  
    /** Return the underlying socket.
115  

151  

116 -
        @return Reference to the underlying tcp_socket.
152 +
        @return Reference to the underlying socket.
117  
    */
153  
    */
118 -
    tcp_socket& socket() noexcept
154 +
    Socket& socket() noexcept
119  
    {
155  
    {
120  
        return sock_;
156  
        return sock_;
121  
    }
157  
    }
122  

158  

123  
    /** Stage data for reads.
159  
    /** Stage data for reads.
124  

160  

125  
        Appends the given string to this mocket's provide buffer.
161  
        Appends the given string to this mocket's provide buffer.
126  
        When `read_some` is called, it will receive this data first
162  
        When `read_some` is called, it will receive this data first
127  
        before reading from the underlying socket.
163  
        before reading from the underlying socket.
128  

164  

129  
        @param s The data to provide.
165  
        @param s The data to provide.
130  

166  

131  
        @pre All coroutines using this mocket must be suspended.
167  
        @pre All coroutines using this mocket must be suspended.
132  
    */
168  
    */
133 -
    void provide(std::string const& s);
169 +
    void provide(std::string const& s)
 
170 +
    {
 
171 +
        provide_.append(s);
 
172 +
    }
134  

173  

135  
    /** Set expected data for writes.
174  
    /** Set expected data for writes.
136  

175  

137  
        Appends the given string to this mocket's expect buffer.
176  
        Appends the given string to this mocket's expect buffer.
138  
        When the caller writes to this mocket, the written data
177  
        When the caller writes to this mocket, the written data
139  
        must match the expected data. On mismatch, `fuse::fail()`
178  
        must match the expected data. On mismatch, `fuse::fail()`
140  
        is called.
179  
        is called.
141  

180  

142  
        @param s The expected data.
181  
        @param s The expected data.
143  

182  

144  
        @pre All coroutines using this mocket must be suspended.
183  
        @pre All coroutines using this mocket must be suspended.
145  
    */
184  
    */
146 -
    void expect(std::string const& s);
185 +
    void expect(std::string const& s)
 
186 +
    {
 
187 +
        expect_.append(s);
 
188 +
    }
147  

189  

148  
    /** Close the mocket and verify test expectations.
190  
    /** Close the mocket and verify test expectations.
149  

191  

150  
        Closes the underlying socket and verifies that both the
192  
        Closes the underlying socket and verifies that both the
151  
        `expect()` and `provide()` buffers are empty. If either
193  
        `expect()` and `provide()` buffers are empty. If either
152  
        buffer contains unconsumed data, returns `test_failure`
194  
        buffer contains unconsumed data, returns `test_failure`
153  
        and calls `fuse::fail()`.
195  
        and calls `fuse::fail()`.
154  

196  

155  
        @return An error code indicating success or failure.
197  
        @return An error code indicating success or failure.
156  
            Returns `error::test_failure` if buffers are not empty.
198  
            Returns `error::test_failure` if buffers are not empty.
157  
    */
199  
    */
158 -
    std::error_code close();
200 +
    std::error_code close()
 
201 +
    {
 
202 +
        if (!sock_.is_open())
 
203 +
            return {};
 
204 +

 
205 +
        if (!expect_.empty())
 
206 +
        {
 
207 +
            fuse_.fail();
 
208 +
            sock_.close();
 
209 +
            return capy::error::test_failure;
 
210 +
        }
 
211 +
        if (!provide_.empty())
 
212 +
        {
 
213 +
            fuse_.fail();
 
214 +
            sock_.close();
 
215 +
            return capy::error::test_failure;
 
216 +
        }
 
217 +

 
218 +
        sock_.close();
 
219 +
        return {};
 
220 +
    }
159  

221  

160  
    /** Cancel pending I/O operations.
222  
    /** Cancel pending I/O operations.
161  

223  

162  
        Cancels any pending asynchronous operations on the underlying
224  
        Cancels any pending asynchronous operations on the underlying
163  
        socket. Outstanding operations complete with `cond::canceled`.
225  
        socket. Outstanding operations complete with `cond::canceled`.
164  
    */
226  
    */
165 -
    void cancel();
227 +
    void cancel()
 
228 +
    {
 
229 +
        sock_.cancel();
 
230 +
    }
166  

231  

167  
    /** Check if the mocket is open.
232  
    /** Check if the mocket is open.
168  

233  

169  
        @return `true` if the mocket is open.
234  
        @return `true` if the mocket is open.
170  
    */
235  
    */
171 -
    bool is_open() const noexcept;
236 +
    bool is_open() const noexcept
 
237 +
    {
 
238 +
        return sock_.is_open();
 
239 +
    }
172  

240  

173  
    /** Initiate an asynchronous read operation.
241  
    /** Initiate an asynchronous read operation.
174  

242  

175  
        Reads available data into the provided buffer sequence. If the
243  
        Reads available data into the provided buffer sequence. If the
176  
        provide buffer has data, it is consumed first. Otherwise, the
244  
        provide buffer has data, it is consumed first. Otherwise, the
177  
        operation delegates to the underlying socket.
245  
        operation delegates to the underlying socket.
178  

246  

179  
        @param buffers The buffer sequence to read data into.
247  
        @param buffers The buffer sequence to read data into.
180  

248  

181  
        @return An awaitable yielding `(error_code, std::size_t)`.
249  
        @return An awaitable yielding `(error_code, std::size_t)`.
182  
    */
250  
    */
183  
    template<class MutableBufferSequence>
251  
    template<class MutableBufferSequence>
184  
    auto read_some(MutableBufferSequence const& buffers)
252  
    auto read_some(MutableBufferSequence const& buffers)
185  
    {
253  
    {
186  
        return read_some_awaitable<MutableBufferSequence>(*this, buffers);
254  
        return read_some_awaitable<MutableBufferSequence>(*this, buffers);
187  
    }
255  
    }
188  

256  

189  
    /** Initiate an asynchronous write operation.
257  
    /** Initiate an asynchronous write operation.
190  

258  

191  
        Writes data from the provided buffer sequence. If the expect
259  
        Writes data from the provided buffer sequence. If the expect
192  
        buffer has data, it is validated. Otherwise, the operation
260  
        buffer has data, it is validated. Otherwise, the operation
193  
        delegates to the underlying socket.
261  
        delegates to the underlying socket.
194  

262  

195  
        @param buffers The buffer sequence containing data to write.
263  
        @param buffers The buffer sequence containing data to write.
196  

264  

197  
        @return An awaitable yielding `(error_code, std::size_t)`.
265  
        @return An awaitable yielding `(error_code, std::size_t)`.
198  
    */
266  
    */
199  
    template<class ConstBufferSequence>
267  
    template<class ConstBufferSequence>
200  
    auto write_some(ConstBufferSequence const& buffers)
268  
    auto write_some(ConstBufferSequence const& buffers)
201  
    {
269  
    {
202  
        return write_some_awaitable<ConstBufferSequence>(*this, buffers);
270  
        return write_some_awaitable<ConstBufferSequence>(*this, buffers);
203  
    }
271  
    }
204  
};
272  
};
205  

273  

 
274 +
/// Default mocket type using `tcp_socket`.
 
275 +
using mocket = basic_mocket<>;
206  

276  

 
277 +
template<class Socket>
207  
template<class MutableBufferSequence>
278  
template<class MutableBufferSequence>
208  
std::size_t
279  
std::size_t
209 -
mocket::consume_provide(MutableBufferSequence const& buffers) noexcept
280 +
basic_mocket<Socket>::consume_provide(
 
281 +
    MutableBufferSequence const& buffers) noexcept
210  
{
282  
{
211  
    auto n =
283  
    auto n =
212  
        capy::buffer_copy(buffers, capy::make_buffer(provide_), max_read_size_);
284  
        capy::buffer_copy(buffers, capy::make_buffer(provide_), max_read_size_);
213  
    provide_.erase(0, n);
285  
    provide_.erase(0, n);
214  
    return n;
286  
    return n;
215  
}
287  
}
216  

288  

 
289 +
template<class Socket>
217  
template<class ConstBufferSequence>
290  
template<class ConstBufferSequence>
218  
bool
291  
bool
219 -
mocket::validate_expect(
292 +
basic_mocket<Socket>::validate_expect(
220  
    ConstBufferSequence const& buffers, std::size_t& bytes_written)
293  
    ConstBufferSequence const& buffers, std::size_t& bytes_written)
221  
{
294  
{
222  
    if (expect_.empty())
295  
    if (expect_.empty())
223  
        return true;
296  
        return true;
224  

297  

225  
    // Build the write data up to max_write_size_
298  
    // Build the write data up to max_write_size_
226  
    std::string written;
299  
    std::string written;
227  
    auto total = capy::buffer_size(buffers);
300  
    auto total = capy::buffer_size(buffers);
228  
    if (total > max_write_size_)
301  
    if (total > max_write_size_)
229  
        total = max_write_size_;
302  
        total = max_write_size_;
230  
    written.resize(total);
303  
    written.resize(total);
231  
    capy::buffer_copy(capy::make_buffer(written), buffers, max_write_size_);
304  
    capy::buffer_copy(capy::make_buffer(written), buffers, max_write_size_);
232  

305  

233  
    // Check if written data matches expect prefix
306  
    // Check if written data matches expect prefix
234  
    auto const match_size = (std::min)(written.size(), expect_.size());
307  
    auto const match_size = (std::min)(written.size(), expect_.size());
235  
    if (std::memcmp(written.data(), expect_.data(), match_size) != 0)
308  
    if (std::memcmp(written.data(), expect_.data(), match_size) != 0)
236  
    {
309  
    {
237  
        fuse_.fail();
310  
        fuse_.fail();
238  
        bytes_written = 0;
311  
        bytes_written = 0;
239  
        return false;
312  
        return false;
240  
    }
313  
    }
241  

314  

242  
    // Consume matched portion
315  
    // Consume matched portion
243  
    expect_.erase(0, match_size);
316  
    expect_.erase(0, match_size);
244  
    bytes_written = written.size();
317  
    bytes_written = written.size();
245  
    return true;
318  
    return true;
246  
}
319  
}
247  

320  

248 -

321 +
template<class Socket>
249  
template<class MutableBufferSequence>
322  
template<class MutableBufferSequence>
250 -
class mocket::read_some_awaitable
323 +
class basic_mocket<Socket>::read_some_awaitable
251  
{
324  
{
252 -
    using sock_awaitable = decltype(std::declval<tcp_socket&>().read_some(
325 +
    using sock_awaitable = decltype(std::declval<Socket&>().read_some(
253  
        std::declval<MutableBufferSequence>()));
326  
        std::declval<MutableBufferSequence>()));
254  

327  

255 -
    mocket* m_;
328 +
    basic_mocket* m_;
256  
    MutableBufferSequence buffers_;
329  
    MutableBufferSequence buffers_;
257  
    std::size_t n_ = 0;
330  
    std::size_t n_ = 0;
258  
    union
331  
    union
259  
    {
332  
    {
260  
        char dummy_;
333  
        char dummy_;
261  
        sock_awaitable underlying_;
334  
        sock_awaitable underlying_;
262  
    };
335  
    };
263  
    bool sync_ = true;
336  
    bool sync_ = true;
264  

337  

265  
public:
338  
public:
266 -
    read_some_awaitable(mocket& m, MutableBufferSequence buffers) noexcept
339 +
    read_some_awaitable(basic_mocket& m, MutableBufferSequence buffers) noexcept
267  
        : m_(&m)
340  
        : m_(&m)
268  
        , buffers_(std::move(buffers))
341  
        , buffers_(std::move(buffers))
269  
    {
342  
    {
270  
    }
343  
    }
271  

344  

272  
    ~read_some_awaitable()
345  
    ~read_some_awaitable()
273  
    {
346  
    {
274  
        if (!sync_)
347  
        if (!sync_)
275  
            underlying_.~sock_awaitable();
348  
            underlying_.~sock_awaitable();
276  
    }
349  
    }
277  

350  

278  
    read_some_awaitable(read_some_awaitable&& other) noexcept
351  
    read_some_awaitable(read_some_awaitable&& other) noexcept
279  
        : m_(other.m_)
352  
        : m_(other.m_)
280  
        , buffers_(std::move(other.buffers_))
353  
        , buffers_(std::move(other.buffers_))
281  
        , n_(other.n_)
354  
        , n_(other.n_)
282  
        , sync_(other.sync_)
355  
        , sync_(other.sync_)
283  
    {
356  
    {
284  
        if (!sync_)
357  
        if (!sync_)
285  
        {
358  
        {
286  
            new (&underlying_) sock_awaitable(std::move(other.underlying_));
359  
            new (&underlying_) sock_awaitable(std::move(other.underlying_));
287  
            other.underlying_.~sock_awaitable();
360  
            other.underlying_.~sock_awaitable();
288  
            other.sync_ = true;
361  
            other.sync_ = true;
289  
        }
362  
        }
290  
    }
363  
    }
291  

364  

292 -
    read_some_awaitable(read_some_awaitable const&) = delete;
365 +
    read_some_awaitable(read_some_awaitable const&)            = delete;
293  
    read_some_awaitable& operator=(read_some_awaitable const&) = delete;
366  
    read_some_awaitable& operator=(read_some_awaitable const&) = delete;
294 -
    read_some_awaitable& operator=(read_some_awaitable&&) = delete;
367 +
    read_some_awaitable& operator=(read_some_awaitable&&)      = delete;
295  

368  

296  
    bool await_ready()
369  
    bool await_ready()
297  
    {
370  
    {
298  
        if (!m_->provide_.empty())
371  
        if (!m_->provide_.empty())
299  
        {
372  
        {
300  
            n_ = m_->consume_provide(buffers_);
373  
            n_ = m_->consume_provide(buffers_);
301  
            return true;
374  
            return true;
302  
        }
375  
        }
303  
        new (&underlying_) sock_awaitable(m_->sock_.read_some(buffers_));
376  
        new (&underlying_) sock_awaitable(m_->sock_.read_some(buffers_));
304  
        sync_ = false;
377  
        sync_ = false;
305  
        return underlying_.await_ready();
378  
        return underlying_.await_ready();
306  
    }
379  
    }
307  

380  

308  
    template<class... Args>
381  
    template<class... Args>
309  
    auto await_suspend(Args&&... args)
382  
    auto await_suspend(Args&&... args)
310  
    {
383  
    {
311  
        return underlying_.await_suspend(std::forward<Args>(args)...);
384  
        return underlying_.await_suspend(std::forward<Args>(args)...);
312  
    }
385  
    }
313  

386  

314  
    capy::io_result<std::size_t> await_resume()
387  
    capy::io_result<std::size_t> await_resume()
315  
    {
388  
    {
316  
        if (sync_)
389  
        if (sync_)
317  
            return {{}, n_};
390  
            return {{}, n_};
318  
        return underlying_.await_resume();
391  
        return underlying_.await_resume();
319  
    }
392  
    }
320  
};
393  
};
321  

394  

322 -

395 +
template<class Socket>
323  
template<class ConstBufferSequence>
396  
template<class ConstBufferSequence>
324 -
class mocket::write_some_awaitable
397 +
class basic_mocket<Socket>::write_some_awaitable
325  
{
398  
{
326 -
    using sock_awaitable = decltype(std::declval<tcp_socket&>().write_some(
399 +
    using sock_awaitable = decltype(std::declval<Socket&>().write_some(
327  
        std::declval<ConstBufferSequence>()));
400  
        std::declval<ConstBufferSequence>()));
328  

401  

329 -
    mocket* m_;
402 +
    basic_mocket* m_;
330  
    ConstBufferSequence buffers_;
403  
    ConstBufferSequence buffers_;
331  
    std::size_t n_ = 0;
404  
    std::size_t n_ = 0;
332  
    std::error_code ec_;
405  
    std::error_code ec_;
333  
    union
406  
    union
334  
    {
407  
    {
335  
        char dummy_;
408  
        char dummy_;
336  
        sock_awaitable underlying_;
409  
        sock_awaitable underlying_;
337  
    };
410  
    };
338  
    bool sync_ = true;
411  
    bool sync_ = true;
339  

412  

340  
public:
413  
public:
341 -
    write_some_awaitable(mocket& m, ConstBufferSequence buffers) noexcept
414 +
    write_some_awaitable(basic_mocket& m, ConstBufferSequence buffers) noexcept
342  
        : m_(&m)
415  
        : m_(&m)
343  
        , buffers_(std::move(buffers))
416  
        , buffers_(std::move(buffers))
344  
    {
417  
    {
345  
    }
418  
    }
346  

419  

347  
    ~write_some_awaitable()
420  
    ~write_some_awaitable()
348  
    {
421  
    {
349  
        if (!sync_)
422  
        if (!sync_)
350  
            underlying_.~sock_awaitable();
423  
            underlying_.~sock_awaitable();
351  
    }
424  
    }
352  

425  

353  
    write_some_awaitable(write_some_awaitable&& other) noexcept
426  
    write_some_awaitable(write_some_awaitable&& other) noexcept
354  
        : m_(other.m_)
427  
        : m_(other.m_)
355  
        , buffers_(std::move(other.buffers_))
428  
        , buffers_(std::move(other.buffers_))
356  
        , n_(other.n_)
429  
        , n_(other.n_)
357  
        , ec_(other.ec_)
430  
        , ec_(other.ec_)
358  
        , sync_(other.sync_)
431  
        , sync_(other.sync_)
359  
    {
432  
    {
360  
        if (!sync_)
433  
        if (!sync_)
361  
        {
434  
        {
362  
            new (&underlying_) sock_awaitable(std::move(other.underlying_));
435  
            new (&underlying_) sock_awaitable(std::move(other.underlying_));
363  
            other.underlying_.~sock_awaitable();
436  
            other.underlying_.~sock_awaitable();
364  
            other.sync_ = true;
437  
            other.sync_ = true;
365  
        }
438  
        }
366  
    }
439  
    }
367  

440  

368 -
    write_some_awaitable(write_some_awaitable const&) = delete;
441 +
    write_some_awaitable(write_some_awaitable const&)            = delete;
369  
    write_some_awaitable& operator=(write_some_awaitable const&) = delete;
442  
    write_some_awaitable& operator=(write_some_awaitable const&) = delete;
370 -
    write_some_awaitable& operator=(write_some_awaitable&&) = delete;
443 +
    write_some_awaitable& operator=(write_some_awaitable&&)      = delete;
371  

444  

372  
    bool await_ready()
445  
    bool await_ready()
373  
    {
446  
    {
374  
        if (!m_->expect_.empty())
447  
        if (!m_->expect_.empty())
375  
        {
448  
        {
376  
            if (!m_->validate_expect(buffers_, n_))
449  
            if (!m_->validate_expect(buffers_, n_))
377  
            {
450  
            {
378  
                ec_ = capy::error::test_failure;
451  
                ec_ = capy::error::test_failure;
379 -
                n_ = 0;
452 +
                n_  = 0;
380  
            }
453  
            }
381  
            return true;
454  
            return true;
382  
        }
455  
        }
383  
        new (&underlying_) sock_awaitable(m_->sock_.write_some(buffers_));
456  
        new (&underlying_) sock_awaitable(m_->sock_.write_some(buffers_));
384  
        sync_ = false;
457  
        sync_ = false;
385  
        return underlying_.await_ready();
458  
        return underlying_.await_ready();
386  
    }
459  
    }
387  

460  

388  
    template<class... Args>
461  
    template<class... Args>
389  
    auto await_suspend(Args&&... args)
462  
    auto await_suspend(Args&&... args)
390  
    {
463  
    {
391  
        return underlying_.await_suspend(std::forward<Args>(args)...);
464  
        return underlying_.await_suspend(std::forward<Args>(args)...);
392  
    }
465  
    }
393  

466  

394  
    capy::io_result<std::size_t> await_resume()
467  
    capy::io_result<std::size_t> await_resume()
395  
    {
468  
    {
396  
        if (sync_)
469  
        if (sync_)
397  
            return {ec_, n_};
470  
            return {ec_, n_};
398  
        return underlying_.await_resume();
471  
        return underlying_.await_resume();
399  
    }
472  
    }
400  
};
473  
};
401 -

 
402  

474  

403  
/** Create a mocket paired with a socket.
475  
/** Create a mocket paired with a socket.
404  

476  

405 -
    Creates a mocket and a tcp_socket connected via loopback.
477 +
    Creates a mocket and a socket connected via loopback.
406  
    Data written to one can be read from the other.
478  
    Data written to one can be read from the other.
407  

479  

408  
    The mocket has fuse checks enabled via `maybe_fail()` and
480  
    The mocket has fuse checks enabled via `maybe_fail()` and
409  
    supports provide/expect buffers for test instrumentation.
481  
    supports provide/expect buffers for test instrumentation.
410 -
    The tcp_socket is the "peer" end with no test instrumentation.
482 +
    The socket is the "peer" end with no test instrumentation.
411  

483  

412  
    Optional max_read_size and max_write_size parameters limit the
484  
    Optional max_read_size and max_write_size parameters limit the
413  
    number of bytes transferred per I/O operation on the mocket,
485  
    number of bytes transferred per I/O operation on the mocket,
414  
    simulating chunked network delivery for testing purposes.
486  
    simulating chunked network delivery for testing purposes.
415  

487  

416 -
    @param ctx The execution context for the sockets.
488 +
    @tparam Socket The socket type (default `tcp_socket`).
 
489 +
    @tparam Acceptor The acceptor type (default `tcp_acceptor`).
 
490 +

 
491 +
    @param ctx The I/O context for the sockets.
417  
    @param f The fuse for error injection testing.
492  
    @param f The fuse for error injection testing.
418  
    @param max_read_size Maximum bytes per read operation (default unlimited).
493  
    @param max_read_size Maximum bytes per read operation (default unlimited).
419  
    @param max_write_size Maximum bytes per write operation (default unlimited).
494  
    @param max_write_size Maximum bytes per write operation (default unlimited).
420  

495  

421 -
    @return A pair of (mocket, tcp_socket).
496 +
    @return A pair of (mocket, socket).
422  

497  

423  
    @note Mockets are not thread-safe and must be used in a
498  
    @note Mockets are not thread-safe and must be used in a
424  
        single-threaded, deterministic context.
499  
        single-threaded, deterministic context.
425  
*/
500  
*/
426 -
BOOST_COROSIO_DECL
501 +
template<class Socket = tcp_socket, class Acceptor = tcp_acceptor>
427 -
std::pair<mocket, tcp_socket> make_mocket_pair(
502 +
std::pair<basic_mocket<Socket>, Socket>
428 -
    capy::execution_context& ctx,
503 +
make_mocket_pair(
429 -
    capy::test::fuse f = {},
504 +
    io_context& ctx,
430 -
    std::size_t max_read_size = std::size_t(-1),
505 +
    capy::test::fuse f         = {},
431 -
    std::size_t max_write_size = std::size_t(-1));
506 +
    std::size_t max_read_size  = std::size_t(-1),
 
507 +
    std::size_t max_write_size = std::size_t(-1))
 
508 +
{
 
509 +
    auto ex = ctx.get_executor();
 
510 +

 
511 +
    basic_mocket<Socket> m(ctx, std::move(f), max_read_size, max_write_size);
 
512 +

 
513 +
    Socket peer(ctx);
 
514 +

 
515 +
    std::error_code accept_ec;
 
516 +
    std::error_code connect_ec;
 
517 +
    bool accept_done  = false;
 
518 +
    bool connect_done = false;
 
519 +

 
520 +
    Acceptor acc(ctx);
 
521 +
    auto listen_ec = acc.listen(endpoint(ipv4_address::loopback(), 0));
 
522 +
    if (listen_ec)
 
523 +
        throw std::runtime_error(
 
524 +
            "mocket listen failed: " + listen_ec.message());
 
525 +
    auto port = acc.local_endpoint().port();
 
526 +

 
527 +
    peer.open();
 
528 +

 
529 +
    Socket accepted_socket(ctx);
 
530 +

 
531 +
    capy::run_async(ex)(
 
532 +
        [](Acceptor& a, Socket& s, std::error_code& ec_out,
 
533 +
           bool& done_out) -> capy::task<> {
 
534 +
            auto [ec] = co_await a.accept(s);
 
535 +
            ec_out    = ec;
 
536 +
            done_out  = true;
 
537 +
        }(acc, accepted_socket, accept_ec, accept_done));
 
538 +

 
539 +
    capy::run_async(ex)(
 
540 +
        [](Socket& s, endpoint ep, std::error_code& ec_out,
 
541 +
           bool& done_out) -> capy::task<> {
 
542 +
            auto [ec] = co_await s.connect(ep);
 
543 +
            ec_out    = ec;
 
544 +
            done_out  = true;
 
545 +
        }(peer, endpoint(ipv4_address::loopback(), port), connect_ec,
 
546 +
                           connect_done));
 
547 +

 
548 +
    ctx.run();
 
549 +
    ctx.restart();
 
550 +

 
551 +
    if (!accept_done || accept_ec)
 
552 +
    {
 
553 +
        std::fprintf(
 
554 +
            stderr, "make_mocket_pair: accept failed (done=%d, ec=%s)\n",
 
555 +
            accept_done, accept_ec.message().c_str());
 
556 +
        acc.close();
 
557 +
        throw std::runtime_error("mocket accept failed");
 
558 +
    }
 
559 +

 
560 +
    if (!connect_done || connect_ec)
 
561 +
    {
 
562 +
        std::fprintf(
 
563 +
            stderr, "make_mocket_pair: connect failed (done=%d, ec=%s)\n",
 
564 +
            connect_done, connect_ec.message().c_str());
 
565 +
        acc.close();
 
566 +
        accepted_socket.close();
 
567 +
        throw std::runtime_error("mocket connect failed");
 
568 +
    }
 
569 +

 
570 +
    m.socket() = std::move(accepted_socket);
 
571 +

 
572 +
    acc.close();
 
573 +

 
574 +
    return {std::move(m), std::move(peer)};
 
575 +
}
432  

576  

433  
} // namespace boost::corosio::test
577  
} // namespace boost::corosio::test
434  

578  

435  
#endif
579  
#endif