85.29% Lines (145/170) 100.00% Functions (27/27)
TLA Baseline Branch
Line Hits Code Line Hits Code
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   // Copyright (c) 2026 Steve Gerbino
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // 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)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/corosio 8   // Official repository: https://github.com/cppalliance/corosio
9   // 9   //
10   10  
11   #ifndef BOOST_COROSIO_TEST_MOCKET_HPP 11   #ifndef BOOST_COROSIO_TEST_MOCKET_HPP
12   #define BOOST_COROSIO_TEST_MOCKET_HPP 12   #define BOOST_COROSIO_TEST_MOCKET_HPP
13   13  
14   #include <boost/corosio/detail/except.hpp> 14   #include <boost/corosio/detail/except.hpp>
15   #include <boost/corosio/io_context.hpp> 15   #include <boost/corosio/io_context.hpp>
16   #include <boost/corosio/socket_option.hpp> 16   #include <boost/corosio/socket_option.hpp>
17   #include <boost/corosio/tcp_acceptor.hpp> 17   #include <boost/corosio/tcp_acceptor.hpp>
18   #include <boost/corosio/tcp_socket.hpp> 18   #include <boost/corosio/tcp_socket.hpp>
19   #include <boost/capy/buffers/buffer_copy.hpp> 19   #include <boost/capy/buffers/buffer_copy.hpp>
20   #include <boost/capy/buffers/make_buffer.hpp> 20   #include <boost/capy/buffers/make_buffer.hpp>
21   #include <boost/capy/error.hpp> 21   #include <boost/capy/error.hpp>
22   #include <boost/capy/ex/run_async.hpp> 22   #include <boost/capy/ex/run_async.hpp>
23   #include <boost/capy/io_result.hpp> 23   #include <boost/capy/io_result.hpp>
24   #include <boost/capy/task.hpp> 24   #include <boost/capy/task.hpp>
25   #include <boost/capy/test/fuse.hpp> 25   #include <boost/capy/test/fuse.hpp>
26   26  
27   #include <cstddef> 27   #include <cstddef>
28   #include <cstdio> 28   #include <cstdio>
29   #include <cstring> 29   #include <cstring>
30   #include <stdexcept> 30   #include <stdexcept>
31   #include <string> 31   #include <string>
32   #include <system_error> 32   #include <system_error>
33   #include <utility> 33   #include <utility>
34   34  
35   namespace boost::corosio::test { 35   namespace boost::corosio::test {
36   36  
37   /** A mock socket for testing I/O operations. 37   /** A mock socket for testing I/O operations.
38   38  
39   This class provides a testable socket-like interface where data 39   This class provides a testable socket-like interface where data
40   can be staged for reading and expected data can be validated on 40   can be staged for reading and expected data can be validated on
41   writes. A mocket is paired with a regular socket using 41   writes. A mocket is paired with a regular socket using
42   @ref make_mocket_pair, allowing bidirectional communication testing. 42   @ref make_mocket_pair, allowing bidirectional communication testing.
43   43  
44   When reading, data comes from the `provide()` buffer first. 44   When reading, data comes from the `provide()` buffer first.
45   When writing, data is validated against the `expect()` buffer. 45   When writing, data is validated against the `expect()` buffer.
46   Once buffers are exhausted, I/O passes through to the underlying 46   Once buffers are exhausted, I/O passes through to the underlying
47   socket connection. 47   socket connection.
48   48  
49   Satisfies the `capy::Stream` concept. 49   Satisfies the `capy::Stream` concept.
50   50  
51   @tparam Socket The underlying socket type (default `tcp_socket`). 51   @tparam Socket The underlying socket type (default `tcp_socket`).
52   52  
53   @par Thread Safety 53   @par Thread Safety
54   Not thread-safe. All operations must occur on a single thread. 54   Not thread-safe. All operations must occur on a single thread.
55   All coroutines using the mocket must be suspended when calling 55   All coroutines using the mocket must be suspended when calling
56   `expect()` or `provide()`. 56   `expect()` or `provide()`.
57   57  
58   @see make_mocket_pair 58   @see make_mocket_pair
59   */ 59   */
60   template<class Socket = tcp_socket> 60   template<class Socket = tcp_socket>
61   class basic_mocket 61   class basic_mocket
62   { 62   {
63   Socket sock_; 63   Socket sock_;
64   std::string provide_; 64   std::string provide_;
65   std::string expect_; 65   std::string expect_;
66   capy::test::fuse fuse_; 66   capy::test::fuse fuse_;
67   std::size_t max_read_size_; 67   std::size_t max_read_size_;
68   std::size_t max_write_size_; 68   std::size_t max_write_size_;
69   69  
70   template<class MutableBufferSequence> 70   template<class MutableBufferSequence>
71   std::size_t consume_provide(MutableBufferSequence const& buffers) noexcept; 71   std::size_t consume_provide(MutableBufferSequence const& buffers) noexcept;
72   72  
73   template<class ConstBufferSequence> 73   template<class ConstBufferSequence>
74   bool validate_expect( 74   bool validate_expect(
75   ConstBufferSequence const& buffers, std::size_t& bytes_written); 75   ConstBufferSequence const& buffers, std::size_t& bytes_written);
76   76  
77   public: 77   public:
78   template<class MutableBufferSequence> 78   template<class MutableBufferSequence>
79   class read_some_awaitable; 79   class read_some_awaitable;
80   80  
81   template<class ConstBufferSequence> 81   template<class ConstBufferSequence>
82   class write_some_awaitable; 82   class write_some_awaitable;
83   83  
84   /** Destructor. 84   /** Destructor.
85   */ 85   */
HITCBC 86   12 ~basic_mocket() = default; 86   12 ~basic_mocket() = default;
87   87  
88   /** Construct a mocket. 88   /** Construct a mocket.
89   89  
90   @param ctx The execution context for the socket. 90   @param ctx The execution context for the socket.
91   @param f The fuse for error injection testing. 91   @param f The fuse for error injection testing.
92   @param max_read_size Maximum bytes per read operation. 92   @param max_read_size Maximum bytes per read operation.
93   @param max_write_size Maximum bytes per write operation. 93   @param max_write_size Maximum bytes per write operation.
94   */ 94   */
HITCBC 95   6 basic_mocket( 95   6 basic_mocket(
96   capy::execution_context& ctx, 96   capy::execution_context& ctx,
97   capy::test::fuse f = {}, 97   capy::test::fuse f = {},
98   std::size_t max_read_size = std::size_t(-1), 98   std::size_t max_read_size = std::size_t(-1),
99   std::size_t max_write_size = std::size_t(-1)) 99   std::size_t max_write_size = std::size_t(-1))
HITCBC 100   6 : sock_(ctx) 100   6 : sock_(ctx)
HITCBC 101   6 , fuse_(std::move(f)) 101   6 , fuse_(std::move(f))
HITCBC 102   6 , max_read_size_(max_read_size) 102   6 , max_read_size_(max_read_size)
HITCBC 103   6 , max_write_size_(max_write_size) 103   6 , max_write_size_(max_write_size)
104   { 104   {
HITCBC 105   6 if (max_read_size == 0) 105   6 if (max_read_size == 0)
MISUBC 106   detail::throw_logic_error("mocket: max_read_size cannot be 0"); 106   detail::throw_logic_error("mocket: max_read_size cannot be 0");
HITCBC 107   6 if (max_write_size == 0) 107   6 if (max_write_size == 0)
MISUBC 108   detail::throw_logic_error("mocket: max_write_size cannot be 0"); 108   detail::throw_logic_error("mocket: max_write_size cannot be 0");
HITCBC 109   6 } 109   6 }
110   110  
111   /** Move constructor. 111   /** Move constructor.
112   */ 112   */
HITCBC 113   6 basic_mocket(basic_mocket&& other) noexcept 113   6 basic_mocket(basic_mocket&& other) noexcept
HITCBC 114   6 : sock_(std::move(other.sock_)) 114   6 : sock_(std::move(other.sock_))
HITCBC 115   6 , provide_(std::move(other.provide_)) 115   6 , provide_(std::move(other.provide_))
HITCBC 116   6 , expect_(std::move(other.expect_)) 116   6 , expect_(std::move(other.expect_))
HITCBC 117   6 , fuse_(std::move(other.fuse_)) 117   6 , fuse_(std::move(other.fuse_))
HITCBC 118   6 , max_read_size_(other.max_read_size_) 118   6 , max_read_size_(other.max_read_size_)
HITCBC 119   6 , max_write_size_(other.max_write_size_) 119   6 , max_write_size_(other.max_write_size_)
120   { 120   {
HITCBC 121   6 } 121   6 }
122   122  
123   /** Move assignment. 123   /** Move assignment.
124   */ 124   */
125   basic_mocket& operator=(basic_mocket&& other) noexcept 125   basic_mocket& operator=(basic_mocket&& other) noexcept
126   { 126   {
127   if (this != &other) 127   if (this != &other)
128   { 128   {
129   sock_ = std::move(other.sock_); 129   sock_ = std::move(other.sock_);
130   provide_ = std::move(other.provide_); 130   provide_ = std::move(other.provide_);
131   expect_ = std::move(other.expect_); 131   expect_ = std::move(other.expect_);
132   fuse_ = other.fuse_; 132   fuse_ = other.fuse_;
133   max_read_size_ = other.max_read_size_; 133   max_read_size_ = other.max_read_size_;
134   max_write_size_ = other.max_write_size_; 134   max_write_size_ = other.max_write_size_;
135   } 135   }
136   return *this; 136   return *this;
137   } 137   }
138   138  
139   basic_mocket(basic_mocket const&) = delete; 139   basic_mocket(basic_mocket const&) = delete;
140   basic_mocket& operator=(basic_mocket const&) = delete; 140   basic_mocket& operator=(basic_mocket const&) = delete;
141   141  
142   /** Return the execution context. 142   /** Return the execution context.
143   143  
144   @return Reference to the execution context that owns this mocket. 144   @return Reference to the execution context that owns this mocket.
145   */ 145   */
146   capy::execution_context& context() const noexcept 146   capy::execution_context& context() const noexcept
147   { 147   {
148   return sock_.context(); 148   return sock_.context();
149   } 149   }
150   150  
151   /** Return the underlying socket. 151   /** Return the underlying socket.
152   152  
153   @return Reference to the underlying socket. 153   @return Reference to the underlying socket.
154   */ 154   */
HITCBC 155   6 Socket& socket() noexcept 155   6 Socket& socket() noexcept
156   { 156   {
HITCBC 157   6 return sock_; 157   6 return sock_;
158   } 158   }
159   159  
160   /** Stage data for reads. 160   /** Stage data for reads.
161   161  
162   Appends the given string to this mocket's provide buffer. 162   Appends the given string to this mocket's provide buffer.
163   When `read_some` is called, it will receive this data first 163   When `read_some` is called, it will receive this data first
164   before reading from the underlying socket. 164   before reading from the underlying socket.
165   165  
166   @param s The data to provide. 166   @param s The data to provide.
167   167  
168   @pre All coroutines using this mocket must be suspended. 168   @pre All coroutines using this mocket must be suspended.
169   */ 169   */
HITCBC 170   4 void provide(std::string const& s) 170   4 void provide(std::string const& s)
171   { 171   {
HITCBC 172   4 provide_.append(s); 172   4 provide_.append(s);
HITCBC 173   4 } 173   4 }
174   174  
175   /** Set expected data for writes. 175   /** Set expected data for writes.
176   176  
177   Appends the given string to this mocket's expect buffer. 177   Appends the given string to this mocket's expect buffer.
178   When the caller writes to this mocket, the written data 178   When the caller writes to this mocket, the written data
179   must match the expected data. On mismatch, `fuse::fail()` 179   must match the expected data. On mismatch, `fuse::fail()`
180   is called. 180   is called.
181   181  
182   @param s The expected data. 182   @param s The expected data.
183   183  
184   @pre All coroutines using this mocket must be suspended. 184   @pre All coroutines using this mocket must be suspended.
185   */ 185   */
HITCBC 186   4 void expect(std::string const& s) 186   4 void expect(std::string const& s)
187   { 187   {
HITCBC 188   4 expect_.append(s); 188   4 expect_.append(s);
HITCBC 189   4 } 189   4 }
190   190  
191   /** Close the mocket and verify test expectations. 191   /** Close the mocket and verify test expectations.
192   192  
193   Closes the underlying socket and verifies that both the 193   Closes the underlying socket and verifies that both the
194   `expect()` and `provide()` buffers are empty. If either 194   `expect()` and `provide()` buffers are empty. If either
195   buffer contains unconsumed data, returns `test_failure` 195   buffer contains unconsumed data, returns `test_failure`
196   and calls `fuse::fail()`. 196   and calls `fuse::fail()`.
197   197  
198   @return An error code indicating success or failure. 198   @return An error code indicating success or failure.
199   Returns `error::test_failure` if buffers are not empty. 199   Returns `error::test_failure` if buffers are not empty.
200   */ 200   */
HITCBC 201   6 std::error_code close() 201   6 std::error_code close()
202   { 202   {
HITCBC 203   6 if (!sock_.is_open()) 203   6 if (!sock_.is_open())
MISUBC 204   return {}; 204   return {};
205   205  
HITCBC 206   6 if (!expect_.empty()) 206   6 if (!expect_.empty())
207   { 207   {
HITCBC 208   1 fuse_.fail(); 208   1 fuse_.fail();
HITCBC 209   1 sock_.close(); 209   1 sock_.close();
HITCBC 210   1 return capy::error::test_failure; 210   1 return capy::error::test_failure;
211   } 211   }
HITCBC 212   5 if (!provide_.empty()) 212   5 if (!provide_.empty())
213   { 213   {
HITCBC 214   1 fuse_.fail(); 214   1 fuse_.fail();
HITCBC 215   1 sock_.close(); 215   1 sock_.close();
HITCBC 216   1 return capy::error::test_failure; 216   1 return capy::error::test_failure;
217   } 217   }
218   218  
HITCBC 219   4 sock_.close(); 219   4 sock_.close();
HITCBC 220   4 return {}; 220   4 return {};
221   } 221   }
222   222  
223   /** Cancel pending I/O operations. 223   /** Cancel pending I/O operations.
224   224  
225   Cancels any pending asynchronous operations on the underlying 225   Cancels any pending asynchronous operations on the underlying
226   socket. Outstanding operations complete with `cond::canceled`. 226   socket. Outstanding operations complete with `cond::canceled`.
227   */ 227   */
228   void cancel() 228   void cancel()
229   { 229   {
230   sock_.cancel(); 230   sock_.cancel();
231   } 231   }
232   232  
233   /** Check if the mocket is open. 233   /** Check if the mocket is open.
234   234  
235   @return `true` if the mocket is open. 235   @return `true` if the mocket is open.
236   */ 236   */
HITCBC 237   3 bool is_open() const noexcept 237   3 bool is_open() const noexcept
238   { 238   {
HITCBC 239   3 return sock_.is_open(); 239   3 return sock_.is_open();
240   } 240   }
241   241  
242   /** Initiate an asynchronous read operation. 242   /** Initiate an asynchronous read operation.
243   243  
244   Reads available data into the provided buffer sequence. If the 244   Reads available data into the provided buffer sequence. If the
245   provide buffer has data, it is consumed first. Otherwise, the 245   provide buffer has data, it is consumed first. Otherwise, the
246   operation delegates to the underlying socket. 246   operation delegates to the underlying socket.
247   247  
248   @param buffers The buffer sequence to read data into. 248   @param buffers The buffer sequence to read data into.
249   249  
250   @return An awaitable yielding `(error_code, std::size_t)`. 250   @return An awaitable yielding `(error_code, std::size_t)`.
251   */ 251   */
252   template<class MutableBufferSequence> 252   template<class MutableBufferSequence>
HITCBC 253   4 auto read_some(MutableBufferSequence const& buffers) 253   4 auto read_some(MutableBufferSequence const& buffers)
254   { 254   {
HITCBC 255   4 return read_some_awaitable<MutableBufferSequence>(*this, buffers); 255   4 return read_some_awaitable<MutableBufferSequence>(*this, buffers);
256   } 256   }
257   257  
258   /** Initiate an asynchronous write operation. 258   /** Initiate an asynchronous write operation.
259   259  
260   Writes data from the provided buffer sequence. If the expect 260   Writes data from the provided buffer sequence. If the expect
261   buffer has data, it is validated. Otherwise, the operation 261   buffer has data, it is validated. Otherwise, the operation
262   delegates to the underlying socket. 262   delegates to the underlying socket.
263   263  
264   @param buffers The buffer sequence containing data to write. 264   @param buffers The buffer sequence containing data to write.
265   265  
266   @return An awaitable yielding `(error_code, std::size_t)`. 266   @return An awaitable yielding `(error_code, std::size_t)`.
267   */ 267   */
268   template<class ConstBufferSequence> 268   template<class ConstBufferSequence>
HITCBC 269   4 auto write_some(ConstBufferSequence const& buffers) 269   4 auto write_some(ConstBufferSequence const& buffers)
270   { 270   {
HITCBC 271   4 return write_some_awaitable<ConstBufferSequence>(*this, buffers); 271   4 return write_some_awaitable<ConstBufferSequence>(*this, buffers);
272   } 272   }
273   }; 273   };
274   274  
275   /// Default mocket type using `tcp_socket`. 275   /// Default mocket type using `tcp_socket`.
276   using mocket = basic_mocket<>; 276   using mocket = basic_mocket<>;
277   277  
278   template<class Socket> 278   template<class Socket>
279   template<class MutableBufferSequence> 279   template<class MutableBufferSequence>
280   std::size_t 280   std::size_t
HITCBC 281   3 basic_mocket<Socket>::consume_provide( 281   3 basic_mocket<Socket>::consume_provide(
282   MutableBufferSequence const& buffers) noexcept 282   MutableBufferSequence const& buffers) noexcept
283   { 283   {
284   auto n = 284   auto n =
HITCBC 285   3 capy::buffer_copy(buffers, capy::make_buffer(provide_), max_read_size_); 285   3 capy::buffer_copy(buffers, capy::make_buffer(provide_), max_read_size_);
HITCBC 286   3 provide_.erase(0, n); 286   3 provide_.erase(0, n);
HITCBC 287   3 return n; 287   3 return n;
288   } 288   }
289   289  
290   template<class Socket> 290   template<class Socket>
291   template<class ConstBufferSequence> 291   template<class ConstBufferSequence>
292   bool 292   bool
HITCBC 293   3 basic_mocket<Socket>::validate_expect( 293   3 basic_mocket<Socket>::validate_expect(
294   ConstBufferSequence const& buffers, std::size_t& bytes_written) 294   ConstBufferSequence const& buffers, std::size_t& bytes_written)
295   { 295   {
HITCBC 296   3 if (expect_.empty()) 296   3 if (expect_.empty())
MISUBC 297   return true; 297   return true;
298   298  
299   // Build the write data up to max_write_size_ 299   // Build the write data up to max_write_size_
HITCBC 300   3 std::string written; 300   3 std::string written;
HITCBC 301   3 auto total = capy::buffer_size(buffers); 301   3 auto total = capy::buffer_size(buffers);
HITCBC 302   3 if (total > max_write_size_) 302   3 if (total > max_write_size_)
MISUBC 303   total = max_write_size_; 303   total = max_write_size_;
HITCBC 304   3 written.resize(total); 304   3 written.resize(total);
HITCBC 305   3 capy::buffer_copy(capy::make_buffer(written), buffers, max_write_size_); 305   3 capy::buffer_copy(capy::make_buffer(written), buffers, max_write_size_);
306   306  
307   // Check if written data matches expect prefix 307   // Check if written data matches expect prefix
HITCBC 308   3 auto const match_size = (std::min)(written.size(), expect_.size()); 308   3 auto const match_size = (std::min)(written.size(), expect_.size());
HITCBC 309   3 if (std::memcmp(written.data(), expect_.data(), match_size) != 0) 309   3 if (std::memcmp(written.data(), expect_.data(), match_size) != 0)
310   { 310   {
MISUBC 311   fuse_.fail(); 311   fuse_.fail();
MISUBC 312   bytes_written = 0; 312   bytes_written = 0;
MISUBC 313   return false; 313   return false;
314   } 314   }
315   315  
316   // Consume matched portion 316   // Consume matched portion
HITCBC 317   3 expect_.erase(0, match_size); 317   3 expect_.erase(0, match_size);
HITCBC 318   3 bytes_written = written.size(); 318   3 bytes_written = written.size();
HITCBC 319   3 return true; 319   3 return true;
HITCBC 320   3 } 320   3 }
321   321  
322   template<class Socket> 322   template<class Socket>
323   template<class MutableBufferSequence> 323   template<class MutableBufferSequence>
324   class basic_mocket<Socket>::read_some_awaitable 324   class basic_mocket<Socket>::read_some_awaitable
325   { 325   {
326   using sock_awaitable = decltype(std::declval<Socket&>().read_some( 326   using sock_awaitable = decltype(std::declval<Socket&>().read_some(
327   std::declval<MutableBufferSequence>())); 327   std::declval<MutableBufferSequence>()));
328   328  
329   basic_mocket* m_; 329   basic_mocket* m_;
330   MutableBufferSequence buffers_; 330   MutableBufferSequence buffers_;
331   std::size_t n_ = 0; 331   std::size_t n_ = 0;
332   union 332   union
333   { 333   {
334   char dummy_; 334   char dummy_;
335   sock_awaitable underlying_; 335   sock_awaitable underlying_;
336   }; 336   };
337   bool sync_ = true; 337   bool sync_ = true;
338   338  
339   public: 339   public:
HITCBC 340   4 read_some_awaitable(basic_mocket& m, MutableBufferSequence buffers) noexcept 340   4 read_some_awaitable(basic_mocket& m, MutableBufferSequence buffers) noexcept
HITCBC 341   4 : m_(&m) 341   4 : m_(&m)
HITCBC 342   4 , buffers_(std::move(buffers)) 342   4 , buffers_(std::move(buffers))
343   { 343   {
HITCBC 344   4 } 344   4 }
345   345  
HITCBC 346   8 ~read_some_awaitable() 346   8 ~read_some_awaitable()
347   { 347   {
HITCBC 348   8 if (!sync_) 348   8 if (!sync_)
HITCBC 349   1 underlying_.~sock_awaitable(); 349   1 underlying_.~sock_awaitable();
HITCBC 350   8 } 350   8 }
351   351  
HITCBC 352   4 read_some_awaitable(read_some_awaitable&& other) noexcept 352   4 read_some_awaitable(read_some_awaitable&& other) noexcept
HITCBC 353   4 : m_(other.m_) 353   4 : m_(other.m_)
HITCBC 354   4 , buffers_(std::move(other.buffers_)) 354   4 , buffers_(std::move(other.buffers_))
HITCBC 355   4 , n_(other.n_) 355   4 , n_(other.n_)
HITCBC 356   4 , sync_(other.sync_) 356   4 , sync_(other.sync_)
357   { 357   {
HITCBC 358   4 if (!sync_) 358   4 if (!sync_)
359   { 359   {
MISUBC 360   new (&underlying_) sock_awaitable(std::move(other.underlying_)); 360   new (&underlying_) sock_awaitable(std::move(other.underlying_));
MISUBC 361   other.underlying_.~sock_awaitable(); 361   other.underlying_.~sock_awaitable();
MISUBC 362   other.sync_ = true; 362   other.sync_ = true;
363   } 363   }
HITCBC 364   4 } 364   4 }
365   365  
366   read_some_awaitable(read_some_awaitable const&) = delete; 366   read_some_awaitable(read_some_awaitable const&) = delete;
367   read_some_awaitable& operator=(read_some_awaitable const&) = delete; 367   read_some_awaitable& operator=(read_some_awaitable const&) = delete;
368   read_some_awaitable& operator=(read_some_awaitable&&) = delete; 368   read_some_awaitable& operator=(read_some_awaitable&&) = delete;
369   369  
HITCBC 370   4 bool await_ready() 370   4 bool await_ready()
371   { 371   {
HITCBC 372   4 if (!m_->provide_.empty()) 372   4 if (!m_->provide_.empty())
373   { 373   {
HITCBC 374   3 n_ = m_->consume_provide(buffers_); 374   3 n_ = m_->consume_provide(buffers_);
HITCBC 375   3 return true; 375   3 return true;
376   } 376   }
HITCBC 377   1 new (&underlying_) sock_awaitable(m_->sock_.read_some(buffers_)); 377   1 new (&underlying_) sock_awaitable(m_->sock_.read_some(buffers_));
HITCBC 378   1 sync_ = false; 378   1 sync_ = false;
HITCBC 379   1 return underlying_.await_ready(); 379   1 return underlying_.await_ready();
380   } 380   }
381   381  
382   template<class... Args> 382   template<class... Args>
HITCBC 383   1 auto await_suspend(Args&&... args) 383   1 auto await_suspend(Args&&... args)
384   { 384   {
HITCBC 385   1 return underlying_.await_suspend(std::forward<Args>(args)...); 385   1 return underlying_.await_suspend(std::forward<Args>(args)...);
386   } 386   }
387   387  
HITCBC 388   4 capy::io_result<std::size_t> await_resume() 388   4 capy::io_result<std::size_t> await_resume()
389   { 389   {
HITCBC 390   4 if (sync_) 390   4 if (sync_)
HITCBC 391   3 return {{}, n_}; 391   3 return {{}, n_};
HITCBC 392   1 return underlying_.await_resume(); 392   1 return underlying_.await_resume();
393   } 393   }
394   }; 394   };
395   395  
396   template<class Socket> 396   template<class Socket>
397   template<class ConstBufferSequence> 397   template<class ConstBufferSequence>
398   class basic_mocket<Socket>::write_some_awaitable 398   class basic_mocket<Socket>::write_some_awaitable
399   { 399   {
400   using sock_awaitable = decltype(std::declval<Socket&>().write_some( 400   using sock_awaitable = decltype(std::declval<Socket&>().write_some(
401   std::declval<ConstBufferSequence>())); 401   std::declval<ConstBufferSequence>()));
402   402  
403   basic_mocket* m_; 403   basic_mocket* m_;
404   ConstBufferSequence buffers_; 404   ConstBufferSequence buffers_;
405   std::size_t n_ = 0; 405   std::size_t n_ = 0;
406   std::error_code ec_; 406   std::error_code ec_;
407   union 407   union
408   { 408   {
409   char dummy_; 409   char dummy_;
410   sock_awaitable underlying_; 410   sock_awaitable underlying_;
411   }; 411   };
412   bool sync_ = true; 412   bool sync_ = true;
413   413  
414   public: 414   public:
HITCBC 415   4 write_some_awaitable(basic_mocket& m, ConstBufferSequence buffers) noexcept 415   4 write_some_awaitable(basic_mocket& m, ConstBufferSequence buffers) noexcept
HITCBC 416   4 : m_(&m) 416   4 : m_(&m)
HITCBC 417   4 , buffers_(std::move(buffers)) 417   4 , buffers_(std::move(buffers))
418   { 418   {
HITCBC 419   4 } 419   4 }
420   420  
HITCBC 421   8 ~write_some_awaitable() 421   8 ~write_some_awaitable()
422   { 422   {
HITCBC 423   8 if (!sync_) 423   8 if (!sync_)
HITCBC 424   1 underlying_.~sock_awaitable(); 424   1 underlying_.~sock_awaitable();
HITCBC 425   8 } 425   8 }
426   426  
HITCBC 427   4 write_some_awaitable(write_some_awaitable&& other) noexcept 427   4 write_some_awaitable(write_some_awaitable&& other) noexcept
HITCBC 428   4 : m_(other.m_) 428   4 : m_(other.m_)
HITCBC 429   4 , buffers_(std::move(other.buffers_)) 429   4 , buffers_(std::move(other.buffers_))
HITCBC 430   4 , n_(other.n_) 430   4 , n_(other.n_)
HITCBC 431   4 , ec_(other.ec_) 431   4 , ec_(other.ec_)
HITCBC 432   4 , sync_(other.sync_) 432   4 , sync_(other.sync_)
433   { 433   {
HITCBC 434   4 if (!sync_) 434   4 if (!sync_)
435   { 435   {
MISUBC 436   new (&underlying_) sock_awaitable(std::move(other.underlying_)); 436   new (&underlying_) sock_awaitable(std::move(other.underlying_));
MISUBC 437   other.underlying_.~sock_awaitable(); 437   other.underlying_.~sock_awaitable();
MISUBC 438   other.sync_ = true; 438   other.sync_ = true;
439   } 439   }
HITCBC 440   4 } 440   4 }
441   441  
442   write_some_awaitable(write_some_awaitable const&) = delete; 442   write_some_awaitable(write_some_awaitable const&) = delete;
443   write_some_awaitable& operator=(write_some_awaitable const&) = delete; 443   write_some_awaitable& operator=(write_some_awaitable const&) = delete;
444   write_some_awaitable& operator=(write_some_awaitable&&) = delete; 444   write_some_awaitable& operator=(write_some_awaitable&&) = delete;
445   445  
HITCBC 446   4 bool await_ready() 446   4 bool await_ready()
447   { 447   {
HITCBC 448   4 if (!m_->expect_.empty()) 448   4 if (!m_->expect_.empty())
449   { 449   {
HITCBC 450   3 if (!m_->validate_expect(buffers_, n_)) 450   3 if (!m_->validate_expect(buffers_, n_))
451   { 451   {
MISUBC 452   ec_ = capy::error::test_failure; 452   ec_ = capy::error::test_failure;
MISUBC 453   n_ = 0; 453   n_ = 0;
454   } 454   }
HITCBC 455   3 return true; 455   3 return true;
456   } 456   }
HITCBC 457   1 new (&underlying_) sock_awaitable(m_->sock_.write_some(buffers_)); 457   1 new (&underlying_) sock_awaitable(m_->sock_.write_some(buffers_));
HITCBC 458   1 sync_ = false; 458   1 sync_ = false;
HITCBC 459   1 return underlying_.await_ready(); 459   1 return underlying_.await_ready();
460   } 460   }
461   461  
462   template<class... Args> 462   template<class... Args>
HITCBC 463   1 auto await_suspend(Args&&... args) 463   1 auto await_suspend(Args&&... args)
464   { 464   {
HITCBC 465   1 return underlying_.await_suspend(std::forward<Args>(args)...); 465   1 return underlying_.await_suspend(std::forward<Args>(args)...);
466   } 466   }
467   467  
HITCBC 468   4 capy::io_result<std::size_t> await_resume() 468   4 capy::io_result<std::size_t> await_resume()
469   { 469   {
HITCBC 470   4 if (sync_) 470   4 if (sync_)
HITCBC 471   3 return {ec_, n_}; 471   3 return {ec_, n_};
HITCBC 472   1 return underlying_.await_resume(); 472   1 return underlying_.await_resume();
473   } 473   }
474   }; 474   };
475   475  
476   /** Create a mocket paired with a socket. 476   /** Create a mocket paired with a socket.
477   477  
478   Creates a mocket and a socket connected via loopback. 478   Creates a mocket and a socket connected via loopback.
479   Data written to one can be read from the other. 479   Data written to one can be read from the other.
480   480  
481   The mocket has fuse checks enabled via `maybe_fail()` and 481   The mocket has fuse checks enabled via `maybe_fail()` and
482   supports provide/expect buffers for test instrumentation. 482   supports provide/expect buffers for test instrumentation.
483   The socket is the "peer" end with no test instrumentation. 483   The socket is the "peer" end with no test instrumentation.
484   484  
485   Optional max_read_size and max_write_size parameters limit the 485   Optional max_read_size and max_write_size parameters limit the
486   number of bytes transferred per I/O operation on the mocket, 486   number of bytes transferred per I/O operation on the mocket,
487   simulating chunked network delivery for testing purposes. 487   simulating chunked network delivery for testing purposes.
488   488  
489   @tparam Socket The socket type (default `tcp_socket`). 489   @tparam Socket The socket type (default `tcp_socket`).
490   @tparam Acceptor The acceptor type (default `tcp_acceptor`). 490   @tparam Acceptor The acceptor type (default `tcp_acceptor`).
491   491  
492   @param ctx The I/O context for the sockets. 492   @param ctx The I/O context for the sockets.
493   @param f The fuse for error injection testing. 493   @param f The fuse for error injection testing.
494   @param max_read_size Maximum bytes per read operation (default unlimited). 494   @param max_read_size Maximum bytes per read operation (default unlimited).
495   @param max_write_size Maximum bytes per write operation (default unlimited). 495   @param max_write_size Maximum bytes per write operation (default unlimited).
496   496  
497   @return A pair of (mocket, socket). 497   @return A pair of (mocket, socket).
498   498  
499   @note Mockets are not thread-safe and must be used in a 499   @note Mockets are not thread-safe and must be used in a
500   single-threaded, deterministic context. 500   single-threaded, deterministic context.
501   */ 501   */
502   template<class Socket = tcp_socket, class Acceptor = tcp_acceptor> 502   template<class Socket = tcp_socket, class Acceptor = tcp_acceptor>
503   std::pair<basic_mocket<Socket>, Socket> 503   std::pair<basic_mocket<Socket>, Socket>
HITCBC 504   6 make_mocket_pair( 504   6 make_mocket_pair(
505   io_context& ctx, 505   io_context& ctx,
506   capy::test::fuse f = {}, 506   capy::test::fuse f = {},
507   std::size_t max_read_size = std::size_t(-1), 507   std::size_t max_read_size = std::size_t(-1),
508   std::size_t max_write_size = std::size_t(-1)) 508   std::size_t max_write_size = std::size_t(-1))
509   { 509   {
HITCBC 510   6 auto ex = ctx.get_executor(); 510   6 auto ex = ctx.get_executor();
511   511  
HITCBC 512   6 basic_mocket<Socket> m(ctx, std::move(f), max_read_size, max_write_size); 512   6 basic_mocket<Socket> m(ctx, std::move(f), max_read_size, max_write_size);
513   513  
HITCBC 514   6 Socket peer(ctx); 514   6 Socket peer(ctx);
515   515  
HITCBC 516   6 std::error_code accept_ec; 516   6 std::error_code accept_ec;
HITCBC 517   6 std::error_code connect_ec; 517   6 std::error_code connect_ec;
HITCBC 518   6 bool accept_done = false; 518   6 bool accept_done = false;
HITCBC 519   6 bool connect_done = false; 519   6 bool connect_done = false;
520   520  
HITCBC 521   6 Acceptor acc(ctx); 521   6 Acceptor acc(ctx);
HITCBC 522   6 acc.open(); 522   6 acc.open();
HITCBC 523   6 acc.set_option(socket_option::reuse_address(true)); 523   6 acc.set_option(socket_option::reuse_address(true));
HITCBC 524   6 if (auto bind_ec = acc.bind(endpoint(ipv4_address::loopback(), 0))) 524   6 if (auto bind_ec = acc.bind(endpoint(ipv4_address::loopback(), 0)))
MISUBC 525   throw std::runtime_error("mocket bind failed: " + bind_ec.message()); 525   throw std::runtime_error("mocket bind failed: " + bind_ec.message());
HITCBC 526   6 if (auto listen_ec = acc.listen()) 526   6 if (auto listen_ec = acc.listen())
MISUBC 527   throw std::runtime_error( 527   throw std::runtime_error(
528   "mocket listen failed: " + listen_ec.message()); 528   "mocket listen failed: " + listen_ec.message());
HITCBC 529   6 auto port = acc.local_endpoint().port(); 529   6 auto port = acc.local_endpoint().port();
530   530  
HITCBC 531   6 peer.open(); 531   6 peer.open();
532   532  
HITCBC 533   6 Socket accepted_socket(ctx); 533   6 Socket accepted_socket(ctx);
534   534  
HITCBC 535   6 capy::run_async(ex)( 535   6 capy::run_async(ex)(
HITCBC 536   12 [](Acceptor& a, Socket& s, std::error_code& ec_out, 536   12 [](Acceptor& a, Socket& s, std::error_code& ec_out,
537   bool& done_out) -> capy::task<> { 537   bool& done_out) -> capy::task<> {
538   auto [ec] = co_await a.accept(s); 538   auto [ec] = co_await a.accept(s);
539   ec_out = ec; 539   ec_out = ec;
540   done_out = true; 540   done_out = true;
541   }(acc, accepted_socket, accept_ec, accept_done)); 541   }(acc, accepted_socket, accept_ec, accept_done));
542   542  
HITCBC 543   6 capy::run_async(ex)( 543   6 capy::run_async(ex)(
HITCBC 544   12 [](Socket& s, endpoint ep, std::error_code& ec_out, 544   12 [](Socket& s, endpoint ep, std::error_code& ec_out,
545   bool& done_out) -> capy::task<> { 545   bool& done_out) -> capy::task<> {
546   auto [ec] = co_await s.connect(ep); 546   auto [ec] = co_await s.connect(ep);
547   ec_out = ec; 547   ec_out = ec;
548   done_out = true; 548   done_out = true;
549   }(peer, endpoint(ipv4_address::loopback(), port), connect_ec, 549   }(peer, endpoint(ipv4_address::loopback(), port), connect_ec,
550   connect_done)); 550   connect_done));
551   551  
HITCBC 552   6 ctx.run(); 552   6 ctx.run();
HITCBC 553   6 ctx.restart(); 553   6 ctx.restart();
554   554  
HITCBC 555   6 if (!accept_done || accept_ec) 555   6 if (!accept_done || accept_ec)
556   { 556   {
MISUBC 557   std::fprintf( 557   std::fprintf(
558   stderr, "make_mocket_pair: accept failed (done=%d, ec=%s)\n", 558   stderr, "make_mocket_pair: accept failed (done=%d, ec=%s)\n",
559   accept_done, accept_ec.message().c_str()); 559   accept_done, accept_ec.message().c_str());
MISUBC 560   acc.close(); 560   acc.close();
MISUBC 561   throw std::runtime_error("mocket accept failed"); 561   throw std::runtime_error("mocket accept failed");
562   } 562   }
563   563  
HITCBC 564   6 if (!connect_done || connect_ec) 564   6 if (!connect_done || connect_ec)
565   { 565   {
MISUBC 566   std::fprintf( 566   std::fprintf(
567   stderr, "make_mocket_pair: connect failed (done=%d, ec=%s)\n", 567   stderr, "make_mocket_pair: connect failed (done=%d, ec=%s)\n",
568   connect_done, connect_ec.message().c_str()); 568   connect_done, connect_ec.message().c_str());
MISUBC 569   acc.close(); 569   acc.close();
MISUBC 570   accepted_socket.close(); 570   accepted_socket.close();
MISUBC 571   throw std::runtime_error("mocket connect failed"); 571   throw std::runtime_error("mocket connect failed");
572   } 572   }
573   573  
HITCBC 574   6 m.socket() = std::move(accepted_socket); 574   6 m.socket() = std::move(accepted_socket);
575   575  
HITCBC 576   6 acc.close(); 576   6 acc.close();
577   577  
HITCBC 578   12 return {std::move(m), std::move(peer)}; 578   12 return {std::move(m), std::move(peer)};
HITCBC 579   6 } 579   6 }
580   580  
581   } // namespace boost::corosio::test 581   } // namespace boost::corosio::test
582   582  
583   #endif 583   #endif