81.82% Lines (153/187) 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   36 ~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   18 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   18 : sock_(ctx)
HITCBC 101   18 , fuse_(std::move(f)) 101   18 , fuse_(std::move(f))
HITCBC 102   18 , max_read_size_(max_read_size) 102   18 , max_read_size_(max_read_size)
HITCBC 103   18 , max_write_size_(max_write_size) 103   18 , max_write_size_(max_write_size)
104   { 104   {
HITCBC 105   18 if (max_read_size == 0) 105   18 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   18 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   18 }
110   110  
111   /** Move constructor. 111   /** Move constructor.
112   */ 112   */
HITCBC 113   18 basic_mocket(basic_mocket&& other) noexcept 113   18 basic_mocket(basic_mocket&& other) noexcept
HITCBC 114   18 : sock_(std::move(other.sock_)) 114   18 : sock_(std::move(other.sock_))
HITCBC 115   18 , provide_(std::move(other.provide_)) 115   18 , provide_(std::move(other.provide_))
HITCBC 116   18 , expect_(std::move(other.expect_)) 116   18 , expect_(std::move(other.expect_))
HITCBC 117   18 , fuse_(std::move(other.fuse_)) 117   18 , fuse_(std::move(other.fuse_))
HITCBC 118   18 , max_read_size_(other.max_read_size_) 118   18 , max_read_size_(other.max_read_size_)
HITCBC 119   18 , max_write_size_(other.max_write_size_) 119   18 , max_write_size_(other.max_write_size_)
120   { 120   {
HITCBC 121   18 } 121   18 }
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   20 Socket& socket() noexcept
156   { 156   {
HITCBC 157   20 return sock_; 157   20 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   9 void provide(std::string const& s)
171   { 171   {
HITCBC 172   9 provide_.append(s); 172   9 provide_.append(s);
HITCBC 173   9 } 173   9 }
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   8 void expect(std::string const& s)
187   { 187   {
HITCBC 188   8 expect_.append(s); 188   8 expect_.append(s);
HITCBC 189   8 } 189   8 }
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   18 std::error_code close()
202   { 202   {
HITCBC 203   18 if (!sock_.is_open()) 203   18 if (!sock_.is_open())
MISUBC 204   return {}; 204   return {};
205   205  
HITCBC 206   18 if (!expect_.empty()) 206   18 if (!expect_.empty())
207   { 207   {
HITCBC 208   2 fuse_.fail(); 208   2 fuse_.fail();
HITCBC 209   2 sock_.close(); 209   2 sock_.close();
HITCBC 210   2 return capy::error::test_failure; 210   2 return capy::error::test_failure;
211   } 211   }
HITCBC 212   16 if (!provide_.empty()) 212   16 if (!provide_.empty())
213   { 213   {
HITCBC 214   2 fuse_.fail(); 214   2 fuse_.fail();
HITCBC 215   2 sock_.close(); 215   2 sock_.close();
HITCBC 216   2 return capy::error::test_failure; 216   2 return capy::error::test_failure;
217   } 217   }
218   218  
HITCBC 219   14 sock_.close(); 219   14 sock_.close();
HITCBC 220   14 return {}; 220   14 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   5 bool is_open() const noexcept
238   { 238   {
HITCBC 239   5 return sock_.is_open(); 239   5 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   11 auto read_some(MutableBufferSequence const& buffers)
254   { 254   {
HITCBC 255   11 return read_some_awaitable<MutableBufferSequence>(*this, buffers); 255   11 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   8 auto write_some(ConstBufferSequence const& buffers)
270   { 270   {
HITCBC 271   8 return write_some_awaitable<ConstBufferSequence>(*this, buffers); 271   8 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   10 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   10 capy::buffer_copy(buffers, capy::make_buffer(provide_), max_read_size_);
HITCBC 286   10 provide_.erase(0, n); 286   10 provide_.erase(0, n);
HITCBC 287   10 return n; 287   10 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   7 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   7 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   7 std::string written;
HITCBC 301   7 auto total = capy::buffer_size(buffers); 301   7 auto total = capy::buffer_size(buffers);
HITCBC 302   7 if (total > max_write_size_) 302   7 if (total > max_write_size_)
HITCBC 303   1 total = max_write_size_; 303   1 total = max_write_size_;
HITCBC 304   7 written.resize(total); 304   7 written.resize(total);
HITCBC 305   7 capy::buffer_copy(capy::make_buffer(written), buffers, max_write_size_); 305   7 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   7 auto const match_size = (std::min)(written.size(), expect_.size());
HITCBC 309   7 if (std::memcmp(written.data(), expect_.data(), match_size) != 0) 309   7 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   7 expect_.erase(0, match_size);
HITCBC 318   7 bytes_written = written.size(); 318   7 bytes_written = written.size();
HITCBC 319   7 return true; 319   7 return true;
HITCBC 320   7 } 320   7 }
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   std::error_code ec_; 332   std::error_code ec_;
333   union 333   union
334   { 334   {
335   char dummy_; 335   char dummy_;
336   sock_awaitable underlying_; 336   sock_awaitable underlying_;
337   }; 337   };
338   bool sync_ = true; 338   bool sync_ = true;
339   339  
340   public: 340   public:
HITCBC 341   11 read_some_awaitable(basic_mocket& m, MutableBufferSequence buffers) noexcept 341   11 read_some_awaitable(basic_mocket& m, MutableBufferSequence buffers) noexcept
HITCBC 342   11 : m_(&m) 342   11 : m_(&m)
HITCBC 343   11 , buffers_(std::move(buffers)) 343   11 , buffers_(std::move(buffers))
344   { 344   {
HITCBC 345   11 } 345   11 }
346   346  
HITCBC 347   22 ~read_some_awaitable() 347   22 ~read_some_awaitable()
348   { 348   {
HITCBC 349   22 if (!sync_) 349   22 if (!sync_)
HITCBC 350   1 underlying_.~sock_awaitable(); 350   1 underlying_.~sock_awaitable();
HITCBC 351   22 } 351   22 }
352   352  
HITCBC 353   11 read_some_awaitable(read_some_awaitable&& other) noexcept 353   11 read_some_awaitable(read_some_awaitable&& other) noexcept
HITCBC 354   11 : m_(other.m_) 354   11 : m_(other.m_)
HITCBC 355   11 , buffers_(std::move(other.buffers_)) 355   11 , buffers_(std::move(other.buffers_))
HITCBC 356   11 , n_(other.n_) 356   11 , n_(other.n_)
HITCBC 357   11 , ec_(other.ec_) 357   11 , ec_(other.ec_)
HITCBC 358   11 , sync_(other.sync_) 358   11 , sync_(other.sync_)
359   { 359   {
HITCBC 360   11 if (!sync_) 360   11 if (!sync_)
361   { 361   {
MISUBC 362   new (&underlying_) sock_awaitable(std::move(other.underlying_)); 362   new (&underlying_) sock_awaitable(std::move(other.underlying_));
MISUBC 363   other.underlying_.~sock_awaitable(); 363   other.underlying_.~sock_awaitable();
MISUBC 364   other.sync_ = true; 364   other.sync_ = true;
365   } 365   }
HITCBC 366   11 } 366   11 }
367   367  
368   read_some_awaitable(read_some_awaitable const&) = delete; 368   read_some_awaitable(read_some_awaitable const&) = delete;
369   read_some_awaitable& operator=(read_some_awaitable const&) = delete; 369   read_some_awaitable& operator=(read_some_awaitable const&) = delete;
370   read_some_awaitable& operator=(read_some_awaitable&&) = delete; 370   read_some_awaitable& operator=(read_some_awaitable&&) = delete;
371   371  
HITCBC 372   11 bool await_ready() 372   11 bool await_ready()
373   { 373   {
374   // Fuse injection point: an armed fuse fails this read as if the 374   // Fuse injection point: an armed fuse fails this read as if the
375   // transport did, so a fault-injection sweep exercises the error 375   // transport did, so a fault-injection sweep exercises the error
376   // path of every read the caller issues. Inert outside armed(). 376   // path of every read the caller issues. Inert outside armed().
377   // A transport reports failure through the result, never by 377   // A transport reports failure through the result, never by
378   // throwing from read_some, so the fuse's exception phase is 378   // throwing from read_some, so the fuse's exception phase is
379   // converted to the same error code its error-code phase yields. 379   // converted to the same error code its error-code phase yields.
HITCBC 380   11 std::error_code fec; 380   11 std::error_code fec;
381   try 381   try
382   { 382   {
HITCBC 383   11 fec = m_->fuse_.maybe_fail(); 383   11 fec = m_->fuse_.maybe_fail();
384   } 384   }
MISUBC 385   catch (std::system_error const& e) 385   catch (std::system_error const& e)
386   { 386   {
MISUBC 387   fec = e.code(); 387   fec = e.code();
388   } 388   }
HITCBC 389   11 if (fec) 389   11 if (fec)
390   { 390   {
MISUBC 391   ec_ = fec; 391   ec_ = fec;
MISUBC 392   n_ = 0; 392   n_ = 0;
MISUBC 393   return true; 393   return true;
394   } 394   }
HITCBC 395   11 if (!m_->provide_.empty()) 395   11 if (!m_->provide_.empty())
396   { 396   {
HITCBC 397   10 n_ = m_->consume_provide(buffers_); 397   10 n_ = m_->consume_provide(buffers_);
HITCBC 398   10 return true; 398   10 return true;
399   } 399   }
HITCBC 400   1 new (&underlying_) sock_awaitable(m_->sock_.read_some(buffers_)); 400   1 new (&underlying_) sock_awaitable(m_->sock_.read_some(buffers_));
HITCBC 401   1 sync_ = false; 401   1 sync_ = false;
HITCBC 402   1 return underlying_.await_ready(); 402   1 return underlying_.await_ready();
403   } 403   }
404   404  
405   template<class... Args> 405   template<class... Args>
HITCBC 406   1 auto await_suspend(Args&&... args) 406   1 auto await_suspend(Args&&... args)
407   { 407   {
HITCBC 408   1 return underlying_.await_suspend(std::forward<Args>(args)...); 408   1 return underlying_.await_suspend(std::forward<Args>(args)...);
409   } 409   }
410   410  
HITCBC 411   11 capy::io_result<std::size_t> await_resume() 411   11 capy::io_result<std::size_t> await_resume()
412   { 412   {
HITCBC 413   11 if (sync_) 413   11 if (sync_)
HITCBC 414   10 return {ec_, n_}; 414   10 return {ec_, n_};
HITCBC 415   1 return underlying_.await_resume(); 415   1 return underlying_.await_resume();
416   } 416   }
417   }; 417   };
418   418  
419   template<class Socket> 419   template<class Socket>
420   template<class ConstBufferSequence> 420   template<class ConstBufferSequence>
421   class basic_mocket<Socket>::write_some_awaitable 421   class basic_mocket<Socket>::write_some_awaitable
422   { 422   {
423   using sock_awaitable = decltype(std::declval<Socket&>().write_some( 423   using sock_awaitable = decltype(std::declval<Socket&>().write_some(
424   std::declval<ConstBufferSequence>())); 424   std::declval<ConstBufferSequence>()));
425   425  
426   basic_mocket* m_; 426   basic_mocket* m_;
427   ConstBufferSequence buffers_; 427   ConstBufferSequence buffers_;
428   std::size_t n_ = 0; 428   std::size_t n_ = 0;
429   std::error_code ec_; 429   std::error_code ec_;
430   union 430   union
431   { 431   {
432   char dummy_; 432   char dummy_;
433   sock_awaitable underlying_; 433   sock_awaitable underlying_;
434   }; 434   };
435   bool sync_ = true; 435   bool sync_ = true;
436   436  
437   public: 437   public:
HITCBC 438   8 write_some_awaitable(basic_mocket& m, ConstBufferSequence buffers) noexcept 438   8 write_some_awaitable(basic_mocket& m, ConstBufferSequence buffers) noexcept
HITCBC 439   8 : m_(&m) 439   8 : m_(&m)
HITCBC 440   8 , buffers_(std::move(buffers)) 440   8 , buffers_(std::move(buffers))
441   { 441   {
HITCBC 442   8 } 442   8 }
443   443  
HITCBC 444   16 ~write_some_awaitable() 444   16 ~write_some_awaitable()
445   { 445   {
HITCBC 446   16 if (!sync_) 446   16 if (!sync_)
HITCBC 447   1 underlying_.~sock_awaitable(); 447   1 underlying_.~sock_awaitable();
HITCBC 448   16 } 448   16 }
449   449  
HITCBC 450   8 write_some_awaitable(write_some_awaitable&& other) noexcept 450   8 write_some_awaitable(write_some_awaitable&& other) noexcept
HITCBC 451   8 : m_(other.m_) 451   8 : m_(other.m_)
HITCBC 452   8 , buffers_(std::move(other.buffers_)) 452   8 , buffers_(std::move(other.buffers_))
HITCBC 453   8 , n_(other.n_) 453   8 , n_(other.n_)
HITCBC 454   8 , ec_(other.ec_) 454   8 , ec_(other.ec_)
HITCBC 455   8 , sync_(other.sync_) 455   8 , sync_(other.sync_)
456   { 456   {
HITCBC 457   8 if (!sync_) 457   8 if (!sync_)
458   { 458   {
MISUBC 459   new (&underlying_) sock_awaitable(std::move(other.underlying_)); 459   new (&underlying_) sock_awaitable(std::move(other.underlying_));
MISUBC 460   other.underlying_.~sock_awaitable(); 460   other.underlying_.~sock_awaitable();
MISUBC 461   other.sync_ = true; 461   other.sync_ = true;
462   } 462   }
HITCBC 463   8 } 463   8 }
464   464  
465   write_some_awaitable(write_some_awaitable const&) = delete; 465   write_some_awaitable(write_some_awaitable const&) = delete;
466   write_some_awaitable& operator=(write_some_awaitable const&) = delete; 466   write_some_awaitable& operator=(write_some_awaitable const&) = delete;
467   write_some_awaitable& operator=(write_some_awaitable&&) = delete; 467   write_some_awaitable& operator=(write_some_awaitable&&) = delete;
468   468  
HITCBC 469   8 bool await_ready() 469   8 bool await_ready()
470   { 470   {
471   // Fuse injection point: an armed fuse fails this write as if the 471   // Fuse injection point: an armed fuse fails this write as if the
472   // transport did, so a fault-injection sweep exercises the error 472   // transport did, so a fault-injection sweep exercises the error
473   // path of every write the caller issues. Inert outside armed(). 473   // path of every write the caller issues. Inert outside armed().
474   // A transport reports failure through the result, never by 474   // A transport reports failure through the result, never by
475   // throwing from write_some, so the fuse's exception phase is 475   // throwing from write_some, so the fuse's exception phase is
476   // converted to the same error code its error-code phase yields. 476   // converted to the same error code its error-code phase yields.
HITCBC 477   8 std::error_code fec; 477   8 std::error_code fec;
478   try 478   try
479   { 479   {
HITCBC 480   8 fec = m_->fuse_.maybe_fail(); 480   8 fec = m_->fuse_.maybe_fail();
481   } 481   }
MISUBC 482   catch (std::system_error const& e) 482   catch (std::system_error const& e)
483   { 483   {
MISUBC 484   fec = e.code(); 484   fec = e.code();
485   } 485   }
HITCBC 486   8 if (fec) 486   8 if (fec)
487   { 487   {
MISUBC 488   ec_ = fec; 488   ec_ = fec;
MISUBC 489   n_ = 0; 489   n_ = 0;
MISUBC 490   return true; 490   return true;
491   } 491   }
HITCBC 492   8 if (!m_->expect_.empty()) 492   8 if (!m_->expect_.empty())
493   { 493   {
HITCBC 494   7 if (!m_->validate_expect(buffers_, n_)) 494   7 if (!m_->validate_expect(buffers_, n_))
495   { 495   {
MISUBC 496   ec_ = capy::error::test_failure; 496   ec_ = capy::error::test_failure;
MISUBC 497   n_ = 0; 497   n_ = 0;
498   } 498   }
HITCBC 499   7 return true; 499   7 return true;
500   } 500   }
HITCBC 501   1 new (&underlying_) sock_awaitable(m_->sock_.write_some(buffers_)); 501   1 new (&underlying_) sock_awaitable(m_->sock_.write_some(buffers_));
HITCBC 502   1 sync_ = false; 502   1 sync_ = false;
HITCBC 503   1 return underlying_.await_ready(); 503   1 return underlying_.await_ready();
504   } 504   }
505   505  
506   template<class... Args> 506   template<class... Args>
HITCBC 507   1 auto await_suspend(Args&&... args) 507   1 auto await_suspend(Args&&... args)
508   { 508   {
HITCBC 509   1 return underlying_.await_suspend(std::forward<Args>(args)...); 509   1 return underlying_.await_suspend(std::forward<Args>(args)...);
510   } 510   }
511   511  
HITCBC 512   8 capy::io_result<std::size_t> await_resume() 512   8 capy::io_result<std::size_t> await_resume()
513   { 513   {
HITCBC 514   8 if (sync_) 514   8 if (sync_)
HITCBC 515   7 return {ec_, n_}; 515   7 return {ec_, n_};
HITCBC 516   1 return underlying_.await_resume(); 516   1 return underlying_.await_resume();
517   } 517   }
518   }; 518   };
519   519  
520   /** Create a mocket paired with a socket. 520   /** Create a mocket paired with a socket.
521   521  
522   Creates a mocket and a socket connected via loopback. 522   Creates a mocket and a socket connected via loopback.
523   Data written to one can be read from the other. 523   Data written to one can be read from the other.
524   524  
525   The mocket has fuse checks enabled via `maybe_fail()` and 525   The mocket has fuse checks enabled via `maybe_fail()` and
526   supports provide/expect buffers for test instrumentation. 526   supports provide/expect buffers for test instrumentation.
527   The socket is the "peer" end with no test instrumentation. 527   The socket is the "peer" end with no test instrumentation.
528   528  
529   Optional max_read_size and max_write_size parameters limit the 529   Optional max_read_size and max_write_size parameters limit the
530   number of bytes transferred per I/O operation on the mocket, 530   number of bytes transferred per I/O operation on the mocket,
531   simulating chunked network delivery for testing purposes. 531   simulating chunked network delivery for testing purposes.
532   532  
533   @tparam Socket The socket type (default `tcp_socket`). 533   @tparam Socket The socket type (default `tcp_socket`).
534   @tparam Acceptor The acceptor type (default `tcp_acceptor`). 534   @tparam Acceptor The acceptor type (default `tcp_acceptor`).
535   535  
536   @param ctx The I/O context for the sockets. 536   @param ctx The I/O context for the sockets.
537   @param f The fuse for error injection testing. 537   @param f The fuse for error injection testing.
538   @param max_read_size Maximum bytes per read operation (default unlimited). 538   @param max_read_size Maximum bytes per read operation (default unlimited).
539   @param max_write_size Maximum bytes per write operation (default unlimited). 539   @param max_write_size Maximum bytes per write operation (default unlimited).
540   540  
541   @return A pair of (mocket, socket). 541   @return A pair of (mocket, socket).
542   542  
543   @note Mockets are not thread-safe and must be used in a 543   @note Mockets are not thread-safe and must be used in a
544   single-threaded, deterministic context. 544   single-threaded, deterministic context.
545   */ 545   */
546   template<class Socket = tcp_socket, class Acceptor = tcp_acceptor> 546   template<class Socket = tcp_socket, class Acceptor = tcp_acceptor>
547   std::pair<basic_mocket<Socket>, Socket> 547   std::pair<basic_mocket<Socket>, Socket>
HITCBC 548   18 make_mocket_pair( 548   18 make_mocket_pair(
549   io_context& ctx, 549   io_context& ctx,
550   capy::test::fuse f = {}, 550   capy::test::fuse f = {},
551   std::size_t max_read_size = std::size_t(-1), 551   std::size_t max_read_size = std::size_t(-1),
552   std::size_t max_write_size = std::size_t(-1)) 552   std::size_t max_write_size = std::size_t(-1))
553   { 553   {
HITCBC 554   18 auto ex = ctx.get_executor(); 554   18 auto ex = ctx.get_executor();
555   555  
HITCBC 556   18 basic_mocket<Socket> m(ctx, std::move(f), max_read_size, max_write_size); 556   18 basic_mocket<Socket> m(ctx, std::move(f), max_read_size, max_write_size);
557   557  
HITCBC 558   18 Socket peer(ctx); 558   18 Socket peer(ctx);
559   559  
HITCBC 560   18 std::error_code accept_ec; 560   18 std::error_code accept_ec;
HITCBC 561   18 std::error_code connect_ec; 561   18 std::error_code connect_ec;
HITCBC 562   18 bool accept_done = false; 562   18 bool accept_done = false;
HITCBC 563   18 bool connect_done = false; 563   18 bool connect_done = false;
564   564  
HITCBC 565   18 Acceptor acc(ctx); 565   18 Acceptor acc(ctx);
HITCBC 566   18 acc.open(); 566   18 acc.open();
HITCBC 567   18 acc.set_option(socket_option::reuse_address(true)); 567   18 acc.set_option(socket_option::reuse_address(true));
HITCBC 568   18 if (auto bind_ec = acc.bind(endpoint(ipv4_address::loopback(), 0))) 568   18 if (auto bind_ec = acc.bind(endpoint(ipv4_address::loopback(), 0)))
MISUBC 569   throw std::runtime_error("mocket bind failed: " + bind_ec.message()); 569   throw std::runtime_error("mocket bind failed: " + bind_ec.message());
HITCBC 570   18 if (auto listen_ec = acc.listen()) 570   18 if (auto listen_ec = acc.listen())
MISUBC 571   throw std::runtime_error( 571   throw std::runtime_error(
572   "mocket listen failed: " + listen_ec.message()); 572   "mocket listen failed: " + listen_ec.message());
HITCBC 573   18 auto port = acc.local_endpoint().port(); 573   18 auto port = acc.local_endpoint().port();
574   574  
HITCBC 575   18 peer.open(); 575   18 peer.open();
576   576  
HITCBC 577   18 Socket accepted_socket(ctx); 577   18 Socket accepted_socket(ctx);
578   578  
HITCBC 579   18 capy::run_async(ex)( 579   18 capy::run_async(ex)(
HITCBC 580   36 [](Acceptor& a, Socket& s, std::error_code& ec_out, 580   36 [](Acceptor& a, Socket& s, std::error_code& ec_out,
581   bool& done_out) -> capy::task<> { 581   bool& done_out) -> capy::task<> {
582   auto [ec] = co_await a.accept(s); 582   auto [ec] = co_await a.accept(s);
583   ec_out = ec; 583   ec_out = ec;
584   done_out = true; 584   done_out = true;
585   }(acc, accepted_socket, accept_ec, accept_done)); 585   }(acc, accepted_socket, accept_ec, accept_done));
586   586  
HITCBC 587   18 capy::run_async(ex)( 587   18 capy::run_async(ex)(
HITCBC 588   36 [](Socket& s, endpoint ep, std::error_code& ec_out, 588   36 [](Socket& s, endpoint ep, std::error_code& ec_out,
589   bool& done_out) -> capy::task<> { 589   bool& done_out) -> capy::task<> {
590   auto [ec] = co_await s.connect(ep); 590   auto [ec] = co_await s.connect(ep);
591   ec_out = ec; 591   ec_out = ec;
592   done_out = true; 592   done_out = true;
593   }(peer, endpoint(ipv4_address::loopback(), port), connect_ec, 593   }(peer, endpoint(ipv4_address::loopback(), port), connect_ec,
594   connect_done)); 594   connect_done));
595   595  
HITCBC 596   18 ctx.run(); 596   18 ctx.run();
HITCBC 597   18 ctx.restart(); 597   18 ctx.restart();
598   598  
HITCBC 599   18 if (!accept_done || accept_ec) 599   18 if (!accept_done || accept_ec)
600   { 600   {
MISUBC 601   std::fprintf( 601   std::fprintf(
602   stderr, "make_mocket_pair: accept failed (done=%d, ec=%s)\n", 602   stderr, "make_mocket_pair: accept failed (done=%d, ec=%s)\n",
603   accept_done, accept_ec.message().c_str()); 603   accept_done, accept_ec.message().c_str());
MISUBC 604   acc.close(); 604   acc.close();
MISUBC 605   throw std::runtime_error("mocket accept failed"); 605   throw std::runtime_error("mocket accept failed");
606   } 606   }
607   607  
HITCBC 608   18 if (!connect_done || connect_ec) 608   18 if (!connect_done || connect_ec)
609   { 609   {
MISUBC 610   std::fprintf( 610   std::fprintf(
611   stderr, "make_mocket_pair: connect failed (done=%d, ec=%s)\n", 611   stderr, "make_mocket_pair: connect failed (done=%d, ec=%s)\n",
612   connect_done, connect_ec.message().c_str()); 612   connect_done, connect_ec.message().c_str());
MISUBC 613   acc.close(); 613   acc.close();
MISUBC 614   accepted_socket.close(); 614   accepted_socket.close();
MISUBC 615   throw std::runtime_error("mocket connect failed"); 615   throw std::runtime_error("mocket connect failed");
616   } 616   }
617   617  
HITCBC 618   18 m.socket() = std::move(accepted_socket); 618   18 m.socket() = std::move(accepted_socket);
619   619  
HITCBC 620   18 acc.close(); 620   18 acc.close();
621   621  
HITCBC 622   36 return {std::move(m), std::move(peer)}; 622   36 return {std::move(m), std::move(peer)};
HITCBC 623   18 } 623   18 }
624   624  
625   } // namespace boost::corosio::test 625   } // namespace boost::corosio::test
626   626  
627   #endif 627   #endif