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