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_READ_SOURCE_HPP
11 : #define BOOST_CAPY_TEST_READ_SOURCE_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/buffers.hpp>
15 : #include <boost/capy/buffers/buffer_copy.hpp>
16 : #include <boost/capy/buffers/make_buffer.hpp>
17 : #include <boost/capy/coro.hpp>
18 : #include <boost/capy/ex/executor_ref.hpp>
19 : #include <boost/capy/io_result.hpp>
20 : #include <boost/capy/error.hpp>
21 : #include <boost/capy/test/fuse.hpp>
22 :
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 source for testing read operations.
32 :
33 : Use this to verify code that performs complete reads without needing
34 : real I/O. Call @ref provide to supply data, then @ref read
35 : to consume it. The associated @ref fuse enables error injection
36 : at controlled points.
37 :
38 : This class satisfies the @ref ReadSource concept by providing both
39 : partial reads via `read_some` (satisfying @ref ReadStream) and
40 : complete reads via `read` that fill the entire buffer sequence
41 : before returning.
42 :
43 : @par Thread Safety
44 : Not thread-safe.
45 :
46 : @par Example
47 : @code
48 : fuse f;
49 : read_source rs( f );
50 : rs.provide( "Hello, " );
51 : rs.provide( "World!" );
52 :
53 : auto r = f.armed( [&]( fuse& ) -> task<void> {
54 : char buf[32];
55 : auto [ec, n] = co_await rs.read(
56 : mutable_buffer( buf, sizeof( buf ) ) );
57 : if( ec )
58 : co_return;
59 : // buf contains "Hello, World!"
60 : } );
61 : @endcode
62 :
63 : @see fuse, ReadSource
64 : */
65 : class read_source
66 : {
67 : fuse f_;
68 : std::string data_;
69 : std::size_t pos_ = 0;
70 : std::size_t max_read_size_;
71 :
72 : public:
73 : /** Construct a read source.
74 :
75 : @param f The fuse used to inject errors during reads.
76 :
77 : @param max_read_size Maximum bytes returned per read.
78 : Use to simulate chunked delivery.
79 : */
80 325 : explicit read_source(
81 : fuse f = {},
82 : std::size_t max_read_size = std::size_t(-1)) noexcept
83 325 : : f_(std::move(f))
84 325 : , max_read_size_(max_read_size)
85 : {
86 325 : }
87 :
88 : /** Append data to be returned by subsequent reads.
89 :
90 : Multiple calls accumulate data that @ref read returns.
91 :
92 : @param sv The data to append.
93 : */
94 : void
95 300 : provide(std::string_view sv)
96 : {
97 300 : data_.append(sv);
98 300 : }
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 reading.
109 : std::size_t
110 8 : available() const noexcept
111 : {
112 8 : return data_.size() - pos_;
113 : }
114 :
115 : /** Asynchronously read some data from the source.
116 :
117 : Transfers up to `buffer_size( buffers )` bytes from the internal
118 : buffer to the provided mutable buffer sequence. If no data
119 : remains, returns `error::eof`. Before every read, the attached
120 : @ref fuse is consulted to possibly inject an error for testing
121 : fault scenarios.
122 :
123 : @param buffers The mutable buffer sequence to receive data.
124 :
125 : @return An awaitable yielding `(error_code,std::size_t)`.
126 :
127 : @see fuse
128 : */
129 : template<MutableBufferSequence MB>
130 : auto
131 50 : read_some(MB buffers)
132 : {
133 : struct awaitable
134 : {
135 : read_source* self_;
136 : MB buffers_;
137 :
138 50 : bool await_ready() const noexcept { return true; }
139 :
140 0 : void await_suspend(
141 : coro,
142 : executor_ref,
143 : std::stop_token) const noexcept
144 : {
145 0 : }
146 :
147 : io_result<std::size_t>
148 50 : await_resume()
149 : {
150 50 : if(buffer_empty(buffers_))
151 0 : return {{}, 0};
152 :
153 50 : auto ec = self_->f_.maybe_fail();
154 36 : if(ec)
155 14 : return {ec, 0};
156 :
157 22 : if(self_->pos_ >= self_->data_.size())
158 2 : return {error::eof, 0};
159 :
160 20 : std::size_t avail = self_->data_.size() - self_->pos_;
161 20 : if(avail > self_->max_read_size_)
162 0 : avail = self_->max_read_size_;
163 20 : auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
164 20 : std::size_t const n = buffer_copy(buffers_, src);
165 20 : self_->pos_ += n;
166 20 : return {{}, n};
167 : }
168 : };
169 50 : return awaitable{this, buffers};
170 : }
171 :
172 : /** Asynchronously read data from the source.
173 :
174 : Fills the entire buffer sequence from the internal data.
175 : If the available data is less than the buffer size, returns
176 : `error::eof` with the number of bytes transferred. Before
177 : every read, the attached @ref fuse is consulted to possibly
178 : inject an error for testing fault scenarios.
179 :
180 : Unlike @ref read_some, this ignores `max_read_size` and
181 : transfers all available data in a single operation, matching
182 : the @ref ReadSource semantic contract.
183 :
184 : @param buffers The mutable buffer sequence to receive data.
185 :
186 : @return An awaitable yielding `(error_code,std::size_t)`.
187 :
188 : @see fuse
189 : */
190 : template<MutableBufferSequence MB>
191 : auto
192 360 : read(MB buffers)
193 : {
194 : struct awaitable
195 : {
196 : read_source* self_;
197 : MB buffers_;
198 :
199 360 : bool await_ready() const noexcept { return true; }
200 :
201 0 : void await_suspend(
202 : coro,
203 : executor_ref,
204 : std::stop_token) const noexcept
205 : {
206 0 : }
207 :
208 : io_result<std::size_t>
209 360 : await_resume()
210 : {
211 360 : if(buffer_empty(buffers_))
212 0 : return {{}, 0};
213 :
214 360 : auto ec = self_->f_.maybe_fail();
215 281 : if(ec)
216 79 : return {ec, 0};
217 :
218 202 : if(self_->pos_ >= self_->data_.size())
219 16 : return {error::eof, 0};
220 :
221 186 : std::size_t avail = self_->data_.size() - self_->pos_;
222 186 : auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
223 186 : std::size_t const n = buffer_copy(buffers_, src);
224 186 : self_->pos_ += n;
225 :
226 186 : if(n < buffer_size(buffers_))
227 76 : return {error::eof, n};
228 110 : return {{}, n};
229 : }
230 : };
231 360 : return awaitable{this, buffers};
232 : }
233 : };
234 :
235 : } // test
236 : } // capy
237 : } // boost
238 :
239 : #endif
|