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   36 ~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   18 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   18 : sock_(ctx) 100   6 : sock_(ctx)
HITCBC 101   18 , fuse_(std::move(f)) 101   6 , fuse_(std::move(f))
HITCBC 102   18 , max_read_size_(max_read_size) 102   6 , max_read_size_(max_read_size)
HITCBC 103   18 , max_write_size_(max_write_size) 103   6 , max_write_size_(max_write_size)
104   { 104   {
HITCBC 105   18 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   18 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   18 } 109   6 }
110   110  
111   /** Move constructor. 111   /** Move constructor.
112   */ 112   */
HITCBC 113   18 basic_mocket(basic_mocket&& other) noexcept 113   6 basic_mocket(basic_mocket&& other) noexcept
HITCBC 114   18 : sock_(std::move(other.sock_)) 114   6 : sock_(std::move(other.sock_))
HITCBC 115   18 , provide_(std::move(other.provide_)) 115   6 , provide_(std::move(other.provide_))
HITCBC 116   18 , expect_(std::move(other.expect_)) 116   6 , expect_(std::move(other.expect_))
HITCBC 117   18 , fuse_(std::move(other.fuse_)) 117   6 , fuse_(std::move(other.fuse_))
HITCBC 118   18 , max_read_size_(other.max_read_size_) 118   6 , max_read_size_(other.max_read_size_)
HITCBC 119   18 , max_write_size_(other.max_write_size_) 119   6 , max_write_size_(other.max_write_size_)
120   { 120   {
HITCBC 121   18 } 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   20 Socket& socket() noexcept 155   6 Socket& socket() noexcept
156   { 156   {
HITCBC 157   20 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   9 void provide(std::string const& s) 170   4 void provide(std::string const& s)
171   { 171   {
HITCBC 172   9 provide_.append(s); 172   4 provide_.append(s);
HITCBC 173   9 } 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   8 void expect(std::string const& s) 186   4 void expect(std::string const& s)
187   { 187   {
HITCBC 188   8 expect_.append(s); 188   4 expect_.append(s);
HITCBC 189   8 } 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   18 std::error_code close() 201   6 std::error_code close()
202   { 202   {
HITCBC 203   18 if (!sock_.is_open()) 203   6 if (!sock_.is_open())
MISUBC 204   return {}; 204   return {};
205   205  
HITCBC 206   18 if (!expect_.empty()) 206   6 if (!expect_.empty())
207   { 207   {
HITCBC 208   2 fuse_.fail(); 208   1 fuse_.fail();
HITCBC 209   2 sock_.close(); 209   1 sock_.close();
HITCBC 210   2 return capy::error::test_failure; 210   1 return capy::error::test_failure;
211   } 211   }
HITCBC 212   16 if (!provide_.empty()) 212   5 if (!provide_.empty())
213   { 213   {
HITCBC 214   2 fuse_.fail(); 214   1 fuse_.fail();
HITCBC 215   2 sock_.close(); 215   1 sock_.close();
HITCBC 216   2 return capy::error::test_failure; 216   1 return capy::error::test_failure;
217   } 217   }
218   218  
HITCBC 219   14 sock_.close(); 219   4 sock_.close();
HITCBC 220   14 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   5 bool is_open() const noexcept 237   3 bool is_open() const noexcept
238   { 238   {
HITCBC 239   5 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   11 auto read_some(MutableBufferSequence const& buffers) 253   4 auto read_some(MutableBufferSequence const& buffers)
254   { 254   {
HITCBC 255   11 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   8 auto write_some(ConstBufferSequence const& buffers) 269   4 auto write_some(ConstBufferSequence const& buffers)
270   { 270   {
HITCBC 271   8 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   10 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   10 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   10 provide_.erase(0, n); 286   3 provide_.erase(0, n);
HITCBC 287   10 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   7 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   7 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   7 std::string written; 300   3 std::string written;
HITCBC 301   7 auto total = capy::buffer_size(buffers); 301   3 auto total = capy::buffer_size(buffers);
HITCBC 302   7 if (total > max_write_size_) 302   3 if (total > max_write_size_)
MISLBC 303   1 total = max_write_size_; 303   total = max_write_size_;
HITCBC 304   7 written.resize(total); 304   3 written.resize(total);
HITCBC 305   7 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   7 auto const match_size = (std::min)(written.size(), expect_.size()); 308   3 auto const match_size = (std::min)(written.size(), expect_.size());
HITCBC 309   7 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   7 expect_.erase(0, match_size); 317   3 expect_.erase(0, match_size);
HITCBC 318   7 bytes_written = written.size(); 318   3 bytes_written = written.size();
HITCBC 319   7 return true; 319   3 return true;
HITCBC 320   7 } 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   11 read_some_awaitable(basic_mocket& m, MutableBufferSequence buffers) noexcept 340   4 read_some_awaitable(basic_mocket& m, MutableBufferSequence buffers) noexcept
HITCBC 341   11 : m_(&m) 341   4 : m_(&m)
HITCBC 342   11 , buffers_(std::move(buffers)) 342   4 , buffers_(std::move(buffers))
343   { 343   {
HITCBC 344   11 } 344   4 }
345   345  
HITCBC 346   22 ~read_some_awaitable() 346   8 ~read_some_awaitable()
347   { 347   {
HITCBC 348   22 if (!sync_) 348   8 if (!sync_)
HITCBC 349   1 underlying_.~sock_awaitable(); 349   1 underlying_.~sock_awaitable();
HITCBC 350   22 } 350   8 }
351   351  
HITCBC 352   11 read_some_awaitable(read_some_awaitable&& other) noexcept 352   4 read_some_awaitable(read_some_awaitable&& other) noexcept
HITCBC 353   11 : m_(other.m_) 353   4 : m_(other.m_)
HITCBC 354   11 , buffers_(std::move(other.buffers_)) 354   4 , buffers_(std::move(other.buffers_))
HITCBC 355   11 , n_(other.n_) 355   4 , n_(other.n_)
HITCBC 356   11 , sync_(other.sync_) 356   4 , sync_(other.sync_)
357   { 357   {
HITCBC 358   11 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   11 } 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   11 bool await_ready() 370   4 bool await_ready()
371   { 371   {
HITCBC 372   11 if (!m_->provide_.empty()) 372   4 if (!m_->provide_.empty())
373   { 373   {
HITCBC 374   10 n_ = m_->consume_provide(buffers_); 374   3 n_ = m_->consume_provide(buffers_);
HITCBC 375   10 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   11 capy::io_result<std::size_t> await_resume() 388   4 capy::io_result<std::size_t> await_resume()
389   { 389   {
HITCBC 390   11 if (sync_) 390   4 if (sync_)
HITCBC 391   10 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   8 write_some_awaitable(basic_mocket& m, ConstBufferSequence buffers) noexcept 415   4 write_some_awaitable(basic_mocket& m, ConstBufferSequence buffers) noexcept
HITCBC 416   8 : m_(&m) 416   4 : m_(&m)
HITCBC 417   8 , buffers_(std::move(buffers)) 417   4 , buffers_(std::move(buffers))
418   { 418   {
HITCBC 419   8 } 419   4 }
420   420  
HITCBC 421   16 ~write_some_awaitable() 421   8 ~write_some_awaitable()
422   { 422   {
HITCBC 423   16 if (!sync_) 423   8 if (!sync_)
HITCBC 424   1 underlying_.~sock_awaitable(); 424   1 underlying_.~sock_awaitable();
HITCBC 425   16 } 425   8 }
426   426  
HITCBC 427   8 write_some_awaitable(write_some_awaitable&& other) noexcept 427   4 write_some_awaitable(write_some_awaitable&& other) noexcept
HITCBC 428   8 : m_(other.m_) 428   4 : m_(other.m_)
HITCBC 429   8 , buffers_(std::move(other.buffers_)) 429   4 , buffers_(std::move(other.buffers_))
HITCBC 430   8 , n_(other.n_) 430   4 , n_(other.n_)
HITCBC 431   8 , ec_(other.ec_) 431   4 , ec_(other.ec_)
HITCBC 432   8 , sync_(other.sync_) 432   4 , sync_(other.sync_)
433   { 433   {
HITCBC 434   8 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   8 } 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   8 bool await_ready() 446   4 bool await_ready()
447   { 447   {
HITCBC 448   8 if (!m_->expect_.empty()) 448   4 if (!m_->expect_.empty())
449   { 449   {
HITCBC 450   7 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   7 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   8 capy::io_result<std::size_t> await_resume() 468   4 capy::io_result<std::size_t> await_resume()
469   { 469   {
HITCBC 470   8 if (sync_) 470   4 if (sync_)
HITCBC 471   7 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   18 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   18 auto ex = ctx.get_executor(); 510   6 auto ex = ctx.get_executor();
511   511  
HITCBC 512   18 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   18 Socket peer(ctx); 514   6 Socket peer(ctx);
515   515  
HITCBC 516   18 std::error_code accept_ec; 516   6 std::error_code accept_ec;
HITCBC 517   18 std::error_code connect_ec; 517   6 std::error_code connect_ec;
HITCBC 518   18 bool accept_done = false; 518   6 bool accept_done = false;
HITCBC 519   18 bool connect_done = false; 519   6 bool connect_done = false;
520   520  
HITCBC 521   18 Acceptor acc(ctx); 521   6 Acceptor acc(ctx);
HITCBC 522   18 acc.open(); 522   6 acc.open();
HITCBC 523   18 acc.set_option(socket_option::reuse_address(true)); 523   6 acc.set_option(socket_option::reuse_address(true));
HITCBC 524   18 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   18 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   18 auto port = acc.local_endpoint().port(); 529   6 auto port = acc.local_endpoint().port();
530   530  
HITCBC 531   18 peer.open(); 531   6 peer.open();
532   532  
HITCBC 533   18 Socket accepted_socket(ctx); 533   6 Socket accepted_socket(ctx);
534   534  
HITCBC 535   18 capy::run_async(ex)( 535   6 capy::run_async(ex)(
HITCBC 536   36 [](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   18 capy::run_async(ex)( 543   6 capy::run_async(ex)(
HITCBC 544   36 [](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   18 ctx.run(); 552   6 ctx.run();
HITCBC 553   18 ctx.restart(); 553   6 ctx.restart();
554   554  
HITCBC 555   18 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   18 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   18 m.socket() = std::move(accepted_socket); 574   6 m.socket() = std::move(accepted_socket);
575   575  
HITCBC 576   18 acc.close(); 576   6 acc.close();
577   577  
HITCBC 578   36 return {std::move(m), std::move(peer)}; 578   12 return {std::move(m), std::move(peer)};
HITCBC 579   18 } 579   6 }
580   580  
581   } // namespace boost::corosio::test 581   } // namespace boost::corosio::test
582   582  
583   #endif 583   #endif