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 : Unlike @ref read_stream which provides partial reads via `read_some`,
39 : this class satisfies the @ref ReadSource concept by providing complete
40 : reads that fill the entire buffer sequence 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 322 : explicit read_source(
80 : fuse& f,
81 : std::size_t max_read_size = std::size_t(-1)) noexcept
82 322 : : f_(&f)
83 322 : , max_read_size_(max_read_size)
84 : {
85 322 : }
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 310 : provide(std::string_view sv)
95 : {
96 310 : data_.append(sv);
97 310 : }
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 4 : available() const noexcept
110 : {
111 4 : return data_.size() - pos_;
112 : }
113 :
114 : /** Asynchronously read data from the source.
115 :
116 : Transfers up to `buffer_size( buffers )` bytes from the internal
117 : buffer to the provided mutable buffer sequence, filling buffers
118 : completely before returning. If no data remains, returns
119 : `error::eof`. Before every read, the attached @ref fuse is
120 : consulted to possibly inject an error for testing fault scenarios.
121 : The returned `std::size_t` is the number of bytes transferred.
122 :
123 : @par Effects
124 : On success, advances the internal read position by the number of
125 : bytes copied. If an error is injected by the fuse, the read position
126 : remains unchanged.
127 :
128 : @par Exception Safety
129 : No-throw guarantee.
130 :
131 : @param buffers The mutable buffer sequence to receive data.
132 :
133 : @return An awaitable yielding `(error_code,std::size_t)`.
134 :
135 : @see fuse
136 : */
137 : template<MutableBufferSequence MB>
138 : auto
139 568 : read(MB buffers)
140 : {
141 : struct awaitable
142 : {
143 : read_source* self_;
144 : MB buffers_;
145 :
146 568 : bool await_ready() const noexcept { return true; }
147 :
148 0 : void await_suspend(
149 : coro,
150 : executor_ref,
151 : std::stop_token) const noexcept
152 : {
153 0 : }
154 :
155 : io_result<std::size_t>
156 568 : await_resume()
157 : {
158 568 : auto ec = self_->f_->maybe_fail();
159 470 : if(ec)
160 98 : return {ec, 0};
161 :
162 372 : if(self_->pos_ >= self_->data_.size())
163 70 : return {error::eof, 0};
164 :
165 302 : std::size_t avail = self_->data_.size() - self_->pos_;
166 302 : if(avail > self_->max_read_size_)
167 84 : avail = self_->max_read_size_;
168 302 : auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
169 302 : std::size_t const n = buffer_copy(buffers_, src);
170 302 : self_->pos_ += n;
171 302 : return {{}, n};
172 : }
173 : };
174 568 : return awaitable{this, buffers};
175 : }
176 : };
177 :
178 : } // test
179 : } // capy
180 : } // boost
181 :
182 : #endif
|