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 <coroutine>
17  
#include <coroutine>
18  
#include <boost/capy/ex/io_env.hpp>
18  
#include <boost/capy/ex/io_env.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 <string>
23  
#include <string>
24  
#include <string_view>
24  
#include <string_view>
25  

25  

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

29  

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

31  

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

36  

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

41  

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

44  

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

51  

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

61  

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

70  

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

73  

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

75  

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

86  

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

88  

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

90  

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

98  

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

106  

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

113  

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

115  

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

121  

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

123  

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

125  

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

136  

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

138  

139  
            void await_suspend(
139  
            void await_suspend(
140  
                std::coroutine_handle<>,
140  
                std::coroutine_handle<>,
141  
                io_env const*) const noexcept
141  
                io_env const*) const noexcept
142  
            {
142  
            {
143  
            }
143  
            }
144  

144  

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

150  

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

154  

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

157  

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

169  

170  
    /** Asynchronously read data from the source.
170  
    /** Asynchronously read data from the source.
171  

171  

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

177  

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

181  

182  
        @param buffers The mutable buffer sequence to receive data.
182  
        @param buffers The mutable buffer sequence to receive data.
183  

183  

184  
        @return An awaitable yielding `(error_code,std::size_t)`.
184  
        @return An awaitable yielding `(error_code,std::size_t)`.
185  

185  

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

196  

197  
            bool await_ready() const noexcept { return true; }
197  
            bool await_ready() const noexcept { return true; }
198  

198  

199  
            void await_suspend(
199  
            void await_suspend(
200  
                std::coroutine_handle<>,
200  
                std::coroutine_handle<>,
201  
                io_env const*) const noexcept
201  
                io_env const*) const noexcept
202  
            {
202  
            {
203  
            }
203  
            }
204  

204  

205  
            io_result<std::size_t>
205  
            io_result<std::size_t>
206  
            await_resume()
206  
            await_resume()
207  
            {
207  
            {
208  
                if(buffer_empty(buffers_))
208  
                if(buffer_empty(buffers_))
209  
                    return {{}, 0};
209  
                    return {{}, 0};
210  

210  

211  
                auto ec = self_->f_.maybe_fail();
211  
                auto ec = self_->f_.maybe_fail();
212  
                if(ec)
212  
                if(ec)
213  
                    return {ec, 0};
213  
                    return {ec, 0};
214  

214  

215  
                if(self_->pos_ >= self_->data_.size())
215  
                if(self_->pos_ >= self_->data_.size())
216  
                    return {error::eof, 0};
216  
                    return {error::eof, 0};
217  

217  

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

222  

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

231  

232  
} // test
232  
} // test
233  
} // capy
233  
} // capy
234  
} // boost
234  
} // boost
235  

235  

236  
#endif
236  
#endif