TLA Line data Source 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_READ_STREAM_HPP
12 : #define BOOST_CAPY_TEST_READ_STREAM_HPP
13 :
14 : #include <boost/capy/detail/config.hpp>
15 : #include <boost/capy/buffers.hpp>
16 : #include <boost/capy/buffers/buffer_copy.hpp>
17 : #include <boost/capy/buffers/make_buffer.hpp>
18 : #include <boost/capy/cond.hpp>
19 : #include <coroutine>
20 : #include <boost/capy/ex/io_env.hpp>
21 : #include <boost/capy/io_result.hpp>
22 : #include <boost/capy/test/fuse.hpp>
23 :
24 : #include <string>
25 : #include <string_view>
26 :
27 : namespace boost {
28 : namespace capy {
29 : namespace test {
30 :
31 : /** Buffers data supplied via `provide`, then hands it out through `read_some`.
32 :
33 : Use this to verify code that performs reads without needing
34 : real I/O. Call @ref provide to supply data, then @ref read_some
35 : to consume it. The associated @ref fuse enables error injection
36 : at controlled points. An optional `max_read_size` constructor
37 : parameter limits bytes per read to simulate chunked delivery.
38 :
39 : This class satisfies the @ref ReadStream concept.
40 :
41 : @par Thread Safety
42 : Not thread-safe.
43 :
44 : @par Example
45 : @par !example example
46 :
47 :
48 : @see fuse, ReadStream
49 : */
50 : class read_stream
51 : {
52 : fuse f_;
53 : std::string data_;
54 : std::size_t pos_ = 0;
55 : std::size_t max_read_size_;
56 :
57 : public:
58 : /** Construct a read stream.
59 :
60 : @param f The fuse used to inject errors during reads.
61 :
62 : @param max_read_size Maximum bytes returned per read.
63 : Use to simulate chunked network delivery.
64 : */
65 HIT 305 : explicit read_stream(
66 : fuse f = {},
67 : std::size_t max_read_size = std::size_t(-1)) noexcept
68 305 : : f_(std::move(f))
69 305 : , max_read_size_(max_read_size)
70 : {
71 305 : }
72 :
73 : /** Append data to be returned by subsequent reads.
74 :
75 : Multiple calls accumulate data that @ref read_some returns.
76 :
77 : @param sv The data to append.
78 : */
79 : void
80 307 : provide(std::string_view sv)
81 : {
82 307 : data_.append(sv);
83 307 : }
84 :
85 : /// Clear all data and reset the read position.
86 : void
87 6 : clear() noexcept
88 : {
89 6 : data_.clear();
90 6 : pos_ = 0;
91 6 : }
92 :
93 : /** Return the number of bytes available for reading.
94 :
95 : @return The number of provided bytes not yet consumed.
96 : */
97 : std::size_t
98 24 : available() const noexcept
99 : {
100 24 : return data_.size() - pos_;
101 : }
102 :
103 : /** Asynchronously read data from the stream.
104 :
105 : Transfers up to `buffer_size( buffers )` bytes from the internal
106 : buffer to the provided mutable buffer sequence. If no data remains,
107 : returns `error::eof`. Before every read, the attached @ref fuse is
108 : consulted to possibly inject an error for testing fault scenarios.
109 : The returned `std::size_t` is the number of bytes transferred.
110 :
111 : @par Effects
112 : On success, advances the internal read position by the number of
113 : bytes copied. If an error is injected by the fuse, the read position
114 : remains unchanged.
115 :
116 : @par Exception Safety
117 : Injected I/O conditions are reported via the `error_code`
118 : component of the result. Throws `std::system_error` only when
119 : the attached @ref fuse is in exception mode and reaches its
120 : failure point; no-throw otherwise.
121 :
122 : @par Cancellation
123 : If the environment's stop token is requested, the read
124 : completes immediately with `error::canceled` and transfers no
125 : data. This lets code under test exercise its cancellation paths.
126 : An empty buffer sequence is a no-op that completes successfully
127 : regardless of the stop token.
128 :
129 : @param buffers The mutable buffer sequence to receive data.
130 :
131 : @return An awaitable that await-returns `(error_code,std::size_t)`.
132 :
133 : @throws std::system_error When the attached @ref fuse is in
134 : exception mode and reaches its failure point.
135 :
136 : @see fuse
137 : */
138 : template<MutableBufferSequence MB>
139 : auto
140 430 : read_some(MB buffers)
141 : {
142 : struct awaitable
143 : {
144 : read_stream* self_;
145 : MB buffers_;
146 : bool canceled_ = false;
147 :
148 430 : bool await_ready() const noexcept { return false; }
149 :
150 : // The operation completes synchronously, but await_suspend
151 : // is the only place io_env is delivered (the promise's
152 : // transform_awaiter forwards it here). Returning false means
153 : // the coroutine does not actually suspend — it resumes
154 : // immediately — so the read still completes synchronously
155 : // while having observed the stop token. See io_env, IoAwaitable.
156 : bool
157 430 : await_suspend(
158 : std::coroutine_handle<>,
159 : io_env const* env) noexcept
160 : {
161 430 : canceled_ = env->stop_token.stop_requested();
162 430 : return false;
163 : }
164 :
165 : [[nodiscard]] io_result<std::size_t>
166 430 : await_resume()
167 : {
168 : // Empty buffer is a no-op regardless of
169 : // stream state, stop token, or fuse.
170 430 : if(buffer_empty(buffers_))
171 7 : return {std::error_code(), 0};
172 :
173 423 : if(canceled_)
174 2 : return {error::canceled, 0};
175 :
176 421 : auto ec = self_->f_.maybe_fail();
177 330 : if(ec)
178 91 : return {ec, 0};
179 :
180 239 : if(self_->pos_ >= self_->data_.size())
181 37 : return {error::eof, 0};
182 :
183 202 : std::size_t avail = self_->data_.size() - self_->pos_;
184 202 : if(avail > self_->max_read_size_)
185 24 : avail = self_->max_read_size_;
186 202 : auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
187 202 : std::size_t const n = buffer_copy(buffers_, src);
188 202 : self_->pos_ += n;
189 202 : return {std::error_code(), n};
190 : }
191 : };
192 430 : return awaitable{this, buffers};
193 : }
194 : };
195 :
196 : } // test
197 : } // capy
198 : } // boost
199 :
200 : #endif
|