Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : //
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)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_TEST_BUFFER_SOURCE_HPP
11 : #define BOOST_CAPY_TEST_BUFFER_SOURCE_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/buffers.hpp>
15 : #include <boost/capy/buffers/make_buffer.hpp>
16 : #include <boost/capy/coro.hpp>
17 : #include <boost/capy/error.hpp>
18 : #include <boost/capy/ex/executor_ref.hpp>
19 : #include <boost/capy/io_result.hpp>
20 : #include <boost/capy/test/fuse.hpp>
21 :
22 : #include <algorithm>
23 : #include <stop_token>
24 : #include <string>
25 : #include <string_view>
26 :
27 : namespace boost {
28 : namespace capy {
29 : namespace test {
30 :
31 : /** A mock buffer source for testing push operations.
32 :
33 : Use this to verify code that transfers data from a buffer source to
34 : a sink without needing real I/O. Call @ref provide to supply data,
35 : then @ref pull to retrieve buffer descriptors. The associated
36 : @ref fuse enables error injection at controlled points.
37 :
38 : This class satisfies the @ref BufferSource concept by providing
39 : a pull interface that fills an array of buffer descriptors and
40 : a consume interface to indicate bytes used.
41 :
42 : @par Thread Safety
43 : Not thread-safe.
44 :
45 : @par Example
46 : @code
47 : fuse f;
48 : buffer_source bs( f );
49 : bs.provide( "Hello, " );
50 : bs.provide( "World!" );
51 :
52 : auto r = f.armed( [&]( fuse& ) -> task<void> {
53 : const_buffer arr[16];
54 : auto [ec, bufs] = co_await bs.pull( arr );
55 : if( ec )
56 : co_return;
57 : // bufs contains buffer descriptors
58 : std::size_t n = buffer_size( bufs );
59 : bs.consume( n );
60 : } );
61 : @endcode
62 :
63 : @see fuse, BufferSource
64 : */
65 : class buffer_source
66 : {
67 : fuse f_;
68 : std::string data_;
69 : std::size_t pos_ = 0;
70 : std::size_t max_pull_size_;
71 :
72 : public:
73 : /** Construct a buffer source.
74 :
75 : @param f The fuse used to inject errors during pulls.
76 :
77 : @param max_pull_size Maximum bytes returned per pull.
78 : Use to simulate chunked delivery.
79 : */
80 302 : explicit buffer_source(
81 : fuse f = {},
82 : std::size_t max_pull_size = std::size_t(-1)) noexcept
83 302 : : f_(std::move(f))
84 302 : , max_pull_size_(max_pull_size)
85 : {
86 302 : }
87 :
88 : /** Append data to be returned by subsequent pulls.
89 :
90 : Multiple calls accumulate data that @ref pull returns.
91 :
92 : @param sv The data to append.
93 : */
94 : void
95 316 : provide(std::string_view sv)
96 : {
97 316 : data_.append(sv);
98 316 : }
99 :
100 : /// Clear all data and reset the read position.
101 : void
102 : clear() noexcept
103 : {
104 : data_.clear();
105 : pos_ = 0;
106 : }
107 :
108 : /// Return the number of bytes available for pulling.
109 : std::size_t
110 : available() const noexcept
111 : {
112 : return data_.size() - pos_;
113 : }
114 :
115 : /** Consume bytes from the source.
116 :
117 : Advances the internal read position by the specified number
118 : of bytes. The next call to @ref pull returns data starting
119 : after the consumed bytes.
120 :
121 : @param n The number of bytes to consume. Must not exceed the
122 : total size of buffers returned by the previous @ref pull.
123 : */
124 : void
125 271 : consume(std::size_t n) noexcept
126 : {
127 271 : pos_ += n;
128 271 : }
129 :
130 : /** Pull buffer data from the source.
131 :
132 : Fills the provided span with buffer descriptors pointing to
133 : internal data starting from the current unconsumed position.
134 : Returns a span of filled buffers. When no data remains,
135 : returns an empty span to signal completion.
136 :
137 : Calling pull multiple times without intervening @ref consume
138 : returns the same data. Use consume to advance past processed
139 : bytes.
140 :
141 : @param dest Span of const_buffer to fill.
142 :
143 : @return An awaitable yielding `(error_code,std::span<const_buffer>)`.
144 :
145 : @see consume, fuse
146 : */
147 : auto
148 552 : pull(std::span<const_buffer> dest)
149 : {
150 : struct awaitable
151 : {
152 : buffer_source* self_;
153 : std::span<const_buffer> dest_;
154 :
155 552 : bool await_ready() const noexcept { return true; }
156 :
157 : // This method is required to satisfy Capy's IoAwaitable concept,
158 : // but is never called because await_ready() returns true.
159 : //
160 : // Capy uses a two-layer awaitable system: the promise's
161 : // await_transform wraps awaitables in a transform_awaiter whose
162 : // standard await_suspend(coroutine_handle) calls this custom
163 : // 3-argument overload, passing the executor and stop_token from
164 : // the coroutine's context. For synchronous test awaitables like
165 : // this one, the coroutine never suspends, so this is not invoked.
166 : // The signature exists to allow the same awaitable type to work
167 : // with both synchronous (test) and asynchronous (real I/O) code.
168 0 : void await_suspend(
169 : coro,
170 : executor_ref,
171 : std::stop_token) const noexcept
172 : {
173 0 : }
174 :
175 : io_result<std::span<const_buffer>>
176 552 : await_resume()
177 : {
178 552 : auto ec = self_->f_.maybe_fail();
179 467 : if(ec)
180 85 : return {ec, {}};
181 :
182 382 : if(self_->pos_ >= self_->data_.size())
183 66 : return {error::eof, {}};
184 :
185 316 : std::size_t avail = self_->data_.size() - self_->pos_;
186 316 : std::size_t to_return = (std::min)(avail, self_->max_pull_size_);
187 :
188 316 : if(dest_.empty())
189 0 : return {{}, {}};
190 :
191 : // Fill a single buffer descriptor
192 316 : dest_[0] = make_buffer(
193 316 : self_->data_.data() + self_->pos_,
194 : to_return);
195 :
196 316 : return {{}, dest_.first(1)};
197 : }
198 : };
199 552 : return awaitable{this, dest};
200 : }
201 : };
202 :
203 : } // test
204 : } // capy
205 : } // boost
206 :
207 : #endif
|