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