91.84% Lines (45/49) 85.71% Functions (12/14)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2 - // Copyright (c) 2026 Michael Vandeberg  
3   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
4   // 3   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 6   //
8   // Official repository: https://github.com/cppalliance/capy 7   // Official repository: https://github.com/cppalliance/capy
9   // 8   //
10   9  
11   #ifndef BOOST_CAPY_TEST_BUFFER_SINK_HPP 10   #ifndef BOOST_CAPY_TEST_BUFFER_SINK_HPP
12   #define BOOST_CAPY_TEST_BUFFER_SINK_HPP 11   #define BOOST_CAPY_TEST_BUFFER_SINK_HPP
13   12  
14   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/buffers.hpp> 14   #include <boost/capy/buffers.hpp>
16   #include <boost/capy/buffers/make_buffer.hpp> 15   #include <boost/capy/buffers/make_buffer.hpp>
17   #include <coroutine> 16   #include <coroutine>
18   #include <boost/capy/ex/io_env.hpp> 17   #include <boost/capy/ex/io_env.hpp>
19   #include <boost/capy/io_result.hpp> 18   #include <boost/capy/io_result.hpp>
20   #include <boost/capy/test/fuse.hpp> 19   #include <boost/capy/test/fuse.hpp>
21   20  
22   #include <algorithm> 21   #include <algorithm>
23   #include <span> 22   #include <span>
24   #include <string> 23   #include <string>
25   #include <string_view> 24   #include <string_view>
26   25  
27   namespace boost { 26   namespace boost {
28   namespace capy { 27   namespace capy {
29   namespace test { 28   namespace test {
30   29  
31   /** A mock buffer sink for testing callee-owns-buffers write operations. 30   /** A mock buffer sink for testing callee-owns-buffers write operations.
32   31  
33   Use this to verify code that writes data using the callee-owns-buffers 32   Use this to verify code that writes data using the callee-owns-buffers
34   pattern without needing real I/O. Call @ref prepare to get writable 33   pattern without needing real I/O. Call @ref prepare to get writable
35   buffers, write into them, then call @ref commit to finalize. The 34   buffers, write into them, then call @ref commit to finalize. The
36   associated @ref fuse enables error injection at controlled points. 35   associated @ref fuse enables error injection at controlled points.
37   36  
38   This class satisfies the @ref BufferSink concept by providing 37   This class satisfies the @ref BufferSink concept by providing
39   internal storage that callers write into directly. 38   internal storage that callers write into directly.
40   39  
41   @par Thread Safety 40   @par Thread Safety
42   Not thread-safe. 41   Not thread-safe.
43   42  
44   @par Example 43   @par Example
45   @code 44   @code
46   fuse f; 45   fuse f;
47   buffer_sink bs( f ); 46   buffer_sink bs( f );
48   47  
49   auto r = f.armed( [&]( fuse& ) -> task<void> { 48   auto r = f.armed( [&]( fuse& ) -> task<void> {
50   mutable_buffer arr[16]; 49   mutable_buffer arr[16];
51   auto bufs = bs.prepare( arr ); 50   auto bufs = bs.prepare( arr );
52   if( bufs.empty() ) 51   if( bufs.empty() )
53   co_return; 52   co_return;
54   53  
55   // Write data into the first prepared buffer 54   // Write data into the first prepared buffer
56   std::memcpy( bufs[0].data(), "Hello", 5 ); 55   std::memcpy( bufs[0].data(), "Hello", 5 );
57   56  
58   auto [ec] = co_await bs.commit( 5 ); 57   auto [ec] = co_await bs.commit( 5 );
59   if( ec ) 58   if( ec )
60   co_return; 59   co_return;
61   60  
62   auto [ec2] = co_await bs.commit_eof( 0 ); 61   auto [ec2] = co_await bs.commit_eof( 0 );
63   // bs.data() returns "Hello" 62   // bs.data() returns "Hello"
64   } ); 63   } );
65   @endcode 64   @endcode
66   65  
67   @see fuse, BufferSink 66   @see fuse, BufferSink
68   */ 67   */
69   class buffer_sink 68   class buffer_sink
70   { 69   {
71   fuse f_; 70   fuse f_;
72   std::string data_; 71   std::string data_;
73   std::string prepare_buf_; 72   std::string prepare_buf_;
74   std::size_t prepare_size_ = 0; 73   std::size_t prepare_size_ = 0;
75   std::size_t max_prepare_size_; 74   std::size_t max_prepare_size_;
76   bool eof_called_ = false; 75   bool eof_called_ = false;
77   76  
78   public: 77   public:
79   /** Construct a buffer sink. 78   /** Construct a buffer sink.
80   79  
81   @param f The fuse used to inject errors during commits. 80   @param f The fuse used to inject errors during commits.
82   81  
83   @param max_prepare_size Maximum bytes available per prepare. 82   @param max_prepare_size Maximum bytes available per prepare.
84   Use to simulate limited buffer space. 83   Use to simulate limited buffer space.
85   */ 84   */
HITCBC 86   560 explicit buffer_sink( 85   558 explicit buffer_sink(
87   fuse f = {}, 86   fuse f = {},
88   std::size_t max_prepare_size = 4096) noexcept 87   std::size_t max_prepare_size = 4096) noexcept
HITCBC 89   560 : f_(std::move(f)) 88   558 : f_(std::move(f))
HITCBC 90   560 , max_prepare_size_(max_prepare_size) 89   558 , max_prepare_size_(max_prepare_size)
91   { 90   {
HITCBC 92   560 prepare_buf_.resize(max_prepare_size_); 91   558 prepare_buf_.resize(max_prepare_size_);
HITCBC 93   560 } 92   558 }
94   93  
95   /// Return the written data as a string view. 94   /// Return the written data as a string view.
96   std::string_view 95   std::string_view
HITCBC 97   82 data() const noexcept 96   82 data() const noexcept
98   { 97   {
HITCBC 99   82 return data_; 98   82 return data_;
100   } 99   }
101   100  
102   /// Return the number of bytes written. 101   /// Return the number of bytes written.
103   std::size_t 102   std::size_t
HITCBC 104   14 size() const noexcept 103   12 size() const noexcept
105   { 104   {
HITCBC 106   14 return data_.size(); 105   12 return data_.size();
107   } 106   }
108   107  
109   /// Return whether commit_eof has been called. 108   /// Return whether commit_eof has been called.
110   bool 109   bool
HITCBC 111   79 eof_called() const noexcept 110   78 eof_called() const noexcept
112   { 111   {
HITCBC 113   79 return eof_called_; 112   78 return eof_called_;
114   } 113   }
115   114  
116   /// Clear all data and reset state. 115   /// Clear all data and reset state.
117   void 116   void
HITCBC 118   2 clear() noexcept 117   2 clear() noexcept
119   { 118   {
HITCBC 120   2 data_.clear(); 119   2 data_.clear();
HITCBC 121   2 prepare_size_ = 0; 120   2 prepare_size_ = 0;
HITCBC 122   2 eof_called_ = false; 121   2 eof_called_ = false;
HITCBC 123   2 } 122   2 }
124   123  
125   /** Prepare writable buffers. 124   /** Prepare writable buffers.
126   125  
127   Fills the provided span with mutable buffer descriptors pointing 126   Fills the provided span with mutable buffer descriptors pointing
128   to internal storage. The caller writes data into these buffers, 127   to internal storage. The caller writes data into these buffers,
129   then calls @ref commit to finalize. 128   then calls @ref commit to finalize.
130   129  
131   @param dest Span of mutable_buffer to fill. 130   @param dest Span of mutable_buffer to fill.
132   131  
133   @return A span of filled buffers (empty or 1 buffer in this implementation). 132   @return A span of filled buffers (empty or 1 buffer in this implementation).
134   */ 133   */
135   std::span<mutable_buffer> 134   std::span<mutable_buffer>
HITCBC 136   842 prepare(std::span<mutable_buffer> dest) 135   840 prepare(std::span<mutable_buffer> dest)
137   { 136   {
HITCBC 138   842 if(dest.empty()) 137   840 if(dest.empty())
HITCBC 139   2 return {}; 138   2 return {};
140   139  
HITCBC 141   840 prepare_size_ = max_prepare_size_; 140   838 prepare_size_ = max_prepare_size_;
HITCBC 142   840 dest[0] = make_buffer(prepare_buf_.data(), prepare_size_); 141   838 dest[0] = make_buffer(prepare_buf_.data(), prepare_size_);
HITCBC 143   840 return dest.first(1); 142   838 return dest.first(1);
144   } 143   }
145   144  
146   /** Commit bytes written to the prepared buffers. 145   /** Commit bytes written to the prepared buffers.
147   146  
148   Transfers `n` bytes from the prepared buffer to the internal 147   Transfers `n` bytes from the prepared buffer to the internal
149   data buffer. Before committing, the attached @ref fuse is 148   data buffer. Before committing, the attached @ref fuse is
150   consulted to possibly inject an error for testing fault scenarios. 149   consulted to possibly inject an error for testing fault scenarios.
151   150  
152   @param n The number of bytes to commit. 151   @param n The number of bytes to commit.
153   152  
154   @return An awaitable that await-returns `(error_code)`. 153   @return An awaitable that await-returns `(error_code)`.
155 - @par Cancellation  
156 - If the environment's stop token has been requested, the commit  
157 - completes immediately with `error::canceled` and commits no data.  
158 -  
159   154  
160   @see fuse 155   @see fuse
161   */ 156   */
162   auto 157   auto
HITCBC 163   739 commit(std::size_t n) 158   738 commit(std::size_t n)
164   { 159   {
165   struct awaitable 160   struct awaitable
166   { 161   {
167   buffer_sink* self_; 162   buffer_sink* self_;
168 - bool canceled_ = false;  
169   std::size_t n_; 163   std::size_t n_;
170   164  
HITCBC 171 - 739 bool await_ready() const noexcept { return false; } 165 + 738 bool await_ready() const noexcept { return true; }
172   166  
173 - // The operation completes synchronously, but await_suspend is 167 + // This method is required to satisfy Capy's IoAwaitable concept,
174 - // the only place io_env is delivered (the promise's 168 + // but is never called because await_ready() returns true.
175 - // transform_awaiter forwards it here). Returning false means 169 + //
176 - // the coroutine does not actually suspend; it resumes 170 + // Capy uses a two-layer awaitable system: the promise's
177 - // immediately, having observed the stop token. See io_env, 171 + // await_transform wraps awaitables in a transform_awaiter whose
178 - // IoAwaitable. 172 + // standard await_suspend(coroutine_handle) calls this custom
179 - bool 173 + // 2-argument overload, passing the io_env from the coroutine's
ECB 180 - 739 await_suspend( 174 + // context. For synchronous test awaitables like this one, the
  175 + // coroutine never suspends, so this is not invoked. The signature
  176 + // exists to allow the same awaitable type to work with both
  177 + // synchronous (test) and asynchronous (real I/O) code.
MISUNC   178 + void await_suspend(
181   std::coroutine_handle<>, 179   std::coroutine_handle<>,
182 - io_env const* env) noexcept 180 + io_env const*) const noexcept
183 - canceled_ = env->stop_token.stop_requested();  
DCB 184 - 739 return false;  
ECB 185   739 { 181   {
MISUIC 186   } 182   }
187   183  
188   io_result<> 184   io_result<>
HITCBC 189   739 await_resume() 185   738 await_resume()
190 - if(canceled_)  
DCB 191 - 739 return {error::canceled};  
DCB 192 - 1  
193   { 186   {
HITCBC 194   738 auto ec = self_->f_.maybe_fail(); 187   738 auto ec = self_->f_.maybe_fail();
HITCBC 195   650 if(ec) 188   650 if(ec)
HITCBC 196   166 return {ec}; 189   166 return {ec};
197   190  
HITCBC 198   484 std::size_t to_commit = (std::min)(n_, self_->prepare_size_); 191   484 std::size_t to_commit = (std::min)(n_, self_->prepare_size_);
HITCBC 199   484 self_->data_.append(self_->prepare_buf_.data(), to_commit); 192   484 self_->data_.append(self_->prepare_buf_.data(), to_commit);
HITCBC 200   484 self_->prepare_size_ = 0; 193   484 self_->prepare_size_ = 0;
201   194  
HITCBC 202   484 return {}; 195   484 return {};
203   } 196   }
204   }; 197   };
HITCBC 205   739 return awaitable{this, n}; 198   738 return awaitable{this, n};
206   } 199   }
207   200  
208   /** Commit final bytes and signal end-of-stream. 201   /** Commit final bytes and signal end-of-stream.
209   202  
210   Transfers `n` bytes from the prepared buffer to the internal 203   Transfers `n` bytes from the prepared buffer to the internal
211   data buffer and marks the sink as finalized. Before committing, 204   data buffer and marks the sink as finalized. Before committing,
212   the attached @ref fuse is consulted to possibly inject an error 205   the attached @ref fuse is consulted to possibly inject an error
213   for testing fault scenarios. 206   for testing fault scenarios.
214   207  
215   @param n The number of bytes to commit. 208   @param n The number of bytes to commit.
216   209  
217   @return An awaitable that await-returns `(error_code)`. 210   @return An awaitable that await-returns `(error_code)`.
218 - @par Cancellation  
219 - If the environment's stop token has been requested, the operation  
220 - completes immediately with `error::canceled`, commits no data, and  
221 - does not signal end-of-stream.  
222 -  
223   211  
224   @see fuse 212   @see fuse
225   */ 213   */
226   auto 214   auto
HITCBC 227   189 commit_eof(std::size_t n) 215   188 commit_eof(std::size_t n)
228   { 216   {
229   struct awaitable 217   struct awaitable
230   { 218   {
231   buffer_sink* self_; 219   buffer_sink* self_;
232 - bool canceled_ = false;  
233   std::size_t n_; 220   std::size_t n_;
234   221  
HITCBC 235 - 189 bool await_ready() const noexcept { return false; } 222 + 188 bool await_ready() const noexcept { return true; }
236   223  
237 - // Reads the stop token without suspending; see the comment 224 + // This method is required to satisfy Capy's IoAwaitable concept,
238 - // on commit() for details. 225 + // but is never called because await_ready() returns true.
239 - bool 226 + // See the comment on commit(std::size_t) for a detailed explanation.
MISLBC 240 - 189 await_suspend( 227 + void await_suspend(
241   std::coroutine_handle<>, 228   std::coroutine_handle<>,
242 - io_env const* env) noexcept 229 + io_env const*) const noexcept
243 - canceled_ = env->stop_token.stop_requested();  
DCB 244 - 189 return false;  
ECB 245   189 { 230   {
MISUIC 246   } 231   }
247   232  
248   io_result<> 233   io_result<>
HITCBC 249   189 await_resume() 234   188 await_resume()
250 - if(canceled_)  
DCB 251 - 189 return {error::canceled};  
DCB 252 - 1  
253   { 235   {
HITCBC 254   188 auto ec = self_->f_.maybe_fail(); 236   188 auto ec = self_->f_.maybe_fail();
HITCBC 255   136 if(ec) 237   136 if(ec)
HITCBC 256   52 return {ec}; 238   52 return {ec};
257   239  
HITCBC 258   84 std::size_t to_commit = (std::min)(n_, self_->prepare_size_); 240   84 std::size_t to_commit = (std::min)(n_, self_->prepare_size_);
HITCBC 259   84 self_->data_.append(self_->prepare_buf_.data(), to_commit); 241   84 self_->data_.append(self_->prepare_buf_.data(), to_commit);
HITCBC 260   84 self_->prepare_size_ = 0; 242   84 self_->prepare_size_ = 0;
261   243  
HITCBC 262   84 self_->eof_called_ = true; 244   84 self_->eof_called_ = true;
HITCBC 263   84 return {}; 245   84 return {};
264   } 246   }
265   }; 247   };
HITCBC 266   189 return awaitable{this, n}; 248   188 return awaitable{this, n};
267   } 249   }
268   }; 250   };
269   251  
270   } // test 252   } // test
271   } // capy 253   } // capy
272   } // boost 254   } // boost
273   255  
274   #endif 256   #endif