1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_TEST_READ_SOURCE_HPP
10  
#ifndef BOOST_CAPY_TEST_READ_SOURCE_HPP
11  
#define BOOST_CAPY_TEST_READ_SOURCE_HPP
11  
#define BOOST_CAPY_TEST_READ_SOURCE_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/buffers.hpp>
14  
#include <boost/capy/buffers.hpp>
15  
#include <boost/capy/buffers/buffer_copy.hpp>
15  
#include <boost/capy/buffers/buffer_copy.hpp>
16  
#include <boost/capy/buffers/make_buffer.hpp>
16  
#include <boost/capy/buffers/make_buffer.hpp>
17  
#include <boost/capy/coro.hpp>
17  
#include <boost/capy/coro.hpp>
18  
#include <boost/capy/ex/executor_ref.hpp>
18  
#include <boost/capy/ex/executor_ref.hpp>
19  
#include <boost/capy/io_result.hpp>
19  
#include <boost/capy/io_result.hpp>
20  
#include <boost/capy/error.hpp>
20  
#include <boost/capy/error.hpp>
21  
#include <boost/capy/test/fuse.hpp>
21  
#include <boost/capy/test/fuse.hpp>
22  

22  

23  
#include <stop_token>
23  
#include <stop_token>
24  
#include <string>
24  
#include <string>
25  
#include <string_view>
25  
#include <string_view>
26  

26  

27  
namespace boost {
27  
namespace boost {
28  
namespace capy {
28  
namespace capy {
29  
namespace test {
29  
namespace test {
30  

30  

31  
/** A mock source for testing read operations.
31  
/** A mock source for testing read operations.
32  

32  

33  
    Use this to verify code that performs complete reads without needing
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
34  
    real I/O. Call @ref provide to supply data, then @ref read
35  
    to consume it. The associated @ref fuse enables error injection
35  
    to consume it. The associated @ref fuse enables error injection
36  
    at controlled points.
36  
    at controlled points.
37  

37  

38  
    This class satisfies the @ref ReadSource concept by providing both
38  
    This class satisfies the @ref ReadSource concept by providing both
39  
    partial reads via `read_some` (satisfying @ref ReadStream) and
39  
    partial reads via `read_some` (satisfying @ref ReadStream) and
40  
    complete reads via `read` that fill the entire buffer sequence
40  
    complete reads via `read` that fill the entire buffer sequence
41  
    before returning.
41  
    before returning.
42  

42  

43  
    @par Thread Safety
43  
    @par Thread Safety
44  
    Not thread-safe.
44  
    Not thread-safe.
45  

45  

46  
    @par Example
46  
    @par Example
47  
    @code
47  
    @code
48  
    fuse f;
48  
    fuse f;
49  
    read_source rs( f );
49  
    read_source rs( f );
50  
    rs.provide( "Hello, " );
50  
    rs.provide( "Hello, " );
51  
    rs.provide( "World!" );
51  
    rs.provide( "World!" );
52  

52  

53  
    auto r = f.armed( [&]( fuse& ) -> task<void> {
53  
    auto r = f.armed( [&]( fuse& ) -> task<void> {
54  
        char buf[32];
54  
        char buf[32];
55  
        auto [ec, n] = co_await rs.read(
55  
        auto [ec, n] = co_await rs.read(
56  
            mutable_buffer( buf, sizeof( buf ) ) );
56  
            mutable_buffer( buf, sizeof( buf ) ) );
57  
        if( ec )
57  
        if( ec )
58  
            co_return;
58  
            co_return;
59  
        // buf contains "Hello, World!"
59  
        // buf contains "Hello, World!"
60  
    } );
60  
    } );
61  
    @endcode
61  
    @endcode
62  

62  

63  
    @see fuse, ReadSource
63  
    @see fuse, ReadSource
64  
*/
64  
*/
65  
class read_source
65  
class read_source
66  
{
66  
{
67  
    fuse f_;
67  
    fuse f_;
68  
    std::string data_;
68  
    std::string data_;
69  
    std::size_t pos_ = 0;
69  
    std::size_t pos_ = 0;
70  
    std::size_t max_read_size_;
70  
    std::size_t max_read_size_;
71  

71  

72  
public:
72  
public:
73  
    /** Construct a read source.
73  
    /** Construct a read source.
74  

74  

75  
        @param f The fuse used to inject errors during reads.
75  
        @param f The fuse used to inject errors during reads.
76  

76  

77  
        @param max_read_size Maximum bytes returned per read.
77  
        @param max_read_size Maximum bytes returned per read.
78  
        Use to simulate chunked delivery.
78  
        Use to simulate chunked delivery.
79  
    */
79  
    */
80  
    explicit read_source(
80  
    explicit read_source(
81  
        fuse f = {},
81  
        fuse f = {},
82  
        std::size_t max_read_size = std::size_t(-1)) noexcept
82  
        std::size_t max_read_size = std::size_t(-1)) noexcept
83  
        : f_(std::move(f))
83  
        : f_(std::move(f))
84  
        , max_read_size_(max_read_size)
84  
        , max_read_size_(max_read_size)
85  
    {
85  
    {
86  
    }
86  
    }
87  

87  

88  
    /** Append data to be returned by subsequent reads.
88  
    /** Append data to be returned by subsequent reads.
89  

89  

90  
        Multiple calls accumulate data that @ref read returns.
90  
        Multiple calls accumulate data that @ref read returns.
91  

91  

92  
        @param sv The data to append.
92  
        @param sv The data to append.
93  
    */
93  
    */
94  
    void
94  
    void
95  
    provide(std::string_view sv)
95  
    provide(std::string_view sv)
96  
    {
96  
    {
97  
        data_.append(sv);
97  
        data_.append(sv);
98  
    }
98  
    }
99  

99  

100  
    /// Clear all data and reset the read position.
100  
    /// Clear all data and reset the read position.
101  
    void
101  
    void
102  
    clear() noexcept
102  
    clear() noexcept
103  
    {
103  
    {
104  
        data_.clear();
104  
        data_.clear();
105  
        pos_ = 0;
105  
        pos_ = 0;
106  
    }
106  
    }
107  

107  

108  
    /// Return the number of bytes available for reading.
108  
    /// Return the number of bytes available for reading.
109  
    std::size_t
109  
    std::size_t
110  
    available() const noexcept
110  
    available() const noexcept
111  
    {
111  
    {
112  
        return data_.size() - pos_;
112  
        return data_.size() - pos_;
113  
    }
113  
    }
114  

114  

115  
    /** Asynchronously read some data from the source.
115  
    /** Asynchronously read some data from the source.
116  

116  

117  
        Transfers up to `buffer_size( buffers )` bytes from the internal
117  
        Transfers up to `buffer_size( buffers )` bytes from the internal
118  
        buffer to the provided mutable buffer sequence. If no data
118  
        buffer to the provided mutable buffer sequence. If no data
119  
        remains, returns `error::eof`. Before every read, the attached
119  
        remains, returns `error::eof`. Before every read, the attached
120  
        @ref fuse is consulted to possibly inject an error for testing
120  
        @ref fuse is consulted to possibly inject an error for testing
121  
        fault scenarios.
121  
        fault scenarios.
122  

122  

123  
        @param buffers The mutable buffer sequence to receive data.
123  
        @param buffers The mutable buffer sequence to receive data.
124  

124  

125  
        @return An awaitable yielding `(error_code,std::size_t)`.
125  
        @return An awaitable yielding `(error_code,std::size_t)`.
126  

126  

127  
        @see fuse
127  
        @see fuse
128  
    */
128  
    */
129  
    template<MutableBufferSequence MB>
129  
    template<MutableBufferSequence MB>
130  
    auto
130  
    auto
131  
    read_some(MB buffers)
131  
    read_some(MB buffers)
132  
    {
132  
    {
133  
        struct awaitable
133  
        struct awaitable
134  
        {
134  
        {
135  
            read_source* self_;
135  
            read_source* self_;
136  
            MB buffers_;
136  
            MB buffers_;
137  

137  

138  
            bool await_ready() const noexcept { return true; }
138  
            bool await_ready() const noexcept { return true; }
139  

139  

140  
            void await_suspend(
140  
            void await_suspend(
141  
                coro,
141  
                coro,
142  
                executor_ref,
142  
                executor_ref,
143  
                std::stop_token) const noexcept
143  
                std::stop_token) const noexcept
144  
            {
144  
            {
145  
            }
145  
            }
146  

146  

147  
            io_result<std::size_t>
147  
            io_result<std::size_t>
148  
            await_resume()
148  
            await_resume()
149  
            {
149  
            {
150  
                if(buffer_empty(buffers_))
150  
                if(buffer_empty(buffers_))
151  
                    return {{}, 0};
151  
                    return {{}, 0};
152  

152  

153  
                auto ec = self_->f_.maybe_fail();
153  
                auto ec = self_->f_.maybe_fail();
154  
                if(ec)
154  
                if(ec)
155  
                    return {ec, 0};
155  
                    return {ec, 0};
156  

156  

157  
                if(self_->pos_ >= self_->data_.size())
157  
                if(self_->pos_ >= self_->data_.size())
158  
                    return {error::eof, 0};
158  
                    return {error::eof, 0};
159  

159  

160  
                std::size_t avail = self_->data_.size() - self_->pos_;
160  
                std::size_t avail = self_->data_.size() - self_->pos_;
161  
                if(avail > self_->max_read_size_)
161  
                if(avail > self_->max_read_size_)
162  
                    avail = self_->max_read_size_;
162  
                    avail = self_->max_read_size_;
163  
                auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
163  
                auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
164  
                std::size_t const n = buffer_copy(buffers_, src);
164  
                std::size_t const n = buffer_copy(buffers_, src);
165  
                self_->pos_ += n;
165  
                self_->pos_ += n;
166  
                return {{}, n};
166  
                return {{}, n};
167  
            }
167  
            }
168  
        };
168  
        };
169  
        return awaitable{this, buffers};
169  
        return awaitable{this, buffers};
170  
    }
170  
    }
171  

171  

172  
    /** Asynchronously read data from the source.
172  
    /** Asynchronously read data from the source.
173  

173  

174  
        Fills the entire buffer sequence from the internal data.
174  
        Fills the entire buffer sequence from the internal data.
175  
        If the available data is less than the buffer size, returns
175  
        If the available data is less than the buffer size, returns
176  
        `error::eof` with the number of bytes transferred. Before
176  
        `error::eof` with the number of bytes transferred. Before
177  
        every read, the attached @ref fuse is consulted to possibly
177  
        every read, the attached @ref fuse is consulted to possibly
178  
        inject an error for testing fault scenarios.
178  
        inject an error for testing fault scenarios.
179  

179  

180  
        Unlike @ref read_some, this ignores `max_read_size` and
180  
        Unlike @ref read_some, this ignores `max_read_size` and
181  
        transfers all available data in a single operation, matching
181  
        transfers all available data in a single operation, matching
182  
        the @ref ReadSource semantic contract.
182  
        the @ref ReadSource semantic contract.
183  

183  

184  
        @param buffers The mutable buffer sequence to receive data.
184  
        @param buffers The mutable buffer sequence to receive data.
185  

185  

186  
        @return An awaitable yielding `(error_code,std::size_t)`.
186  
        @return An awaitable yielding `(error_code,std::size_t)`.
187  

187  

188  
        @see fuse
188  
        @see fuse
189  
    */
189  
    */
190  
    template<MutableBufferSequence MB>
190  
    template<MutableBufferSequence MB>
191  
    auto
191  
    auto
192  
    read(MB buffers)
192  
    read(MB buffers)
193  
    {
193  
    {
194  
        struct awaitable
194  
        struct awaitable
195  
        {
195  
        {
196  
            read_source* self_;
196  
            read_source* self_;
197  
            MB buffers_;
197  
            MB buffers_;
198  

198  

199  
            bool await_ready() const noexcept { return true; }
199  
            bool await_ready() const noexcept { return true; }
200  

200  

201  
            void await_suspend(
201  
            void await_suspend(
202  
                coro,
202  
                coro,
203  
                executor_ref,
203  
                executor_ref,
204  
                std::stop_token) const noexcept
204  
                std::stop_token) const noexcept
205  
            {
205  
            {
206  
            }
206  
            }
207  

207  

208  
            io_result<std::size_t>
208  
            io_result<std::size_t>
209  
            await_resume()
209  
            await_resume()
210  
            {
210  
            {
211  
                if(buffer_empty(buffers_))
211  
                if(buffer_empty(buffers_))
212  
                    return {{}, 0};
212  
                    return {{}, 0};
213  

213  

214  
                auto ec = self_->f_.maybe_fail();
214  
                auto ec = self_->f_.maybe_fail();
215  
                if(ec)
215  
                if(ec)
216  
                    return {ec, 0};
216  
                    return {ec, 0};
217  

217  

218  
                if(self_->pos_ >= self_->data_.size())
218  
                if(self_->pos_ >= self_->data_.size())
219  
                    return {error::eof, 0};
219  
                    return {error::eof, 0};
220  

220  

221  
                std::size_t avail = self_->data_.size() - self_->pos_;
221  
                std::size_t avail = self_->data_.size() - self_->pos_;
222  
                auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
222  
                auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
223  
                std::size_t const n = buffer_copy(buffers_, src);
223  
                std::size_t const n = buffer_copy(buffers_, src);
224  
                self_->pos_ += n;
224  
                self_->pos_ += n;
225  

225  

226  
                if(n < buffer_size(buffers_))
226  
                if(n < buffer_size(buffers_))
227  
                    return {error::eof, n};
227  
                    return {error::eof, n};
228  
                return {{}, n};
228  
                return {{}, n};
229  
            }
229  
            }
230  
        };
230  
        };
231  
        return awaitable{this, buffers};
231  
        return awaitable{this, buffers};
232  
    }
232  
    }
233  
};
233  
};
234  

234  

235  
} // test
235  
} // test
236  
} // capy
236  
} // capy
237  
} // boost
237  
} // boost
238  

238  

239  
#endif
239  
#endif