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