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