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