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