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 -
    Unlike @ref read_stream which provides partial reads via `read_some`,
38 +
    This class satisfies the @ref ReadSource concept by providing both
39 -
    this class satisfies the @ref ReadSource concept by providing complete
39 +
    partial reads via `read_some` (satisfying @ref ReadStream) and
40 -
    reads that fill the entire buffer sequence before returning.
40 +
    complete reads via `read` that fill the entire buffer sequence
 
41 +
    before returning.
41  

42  

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

45  

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

52  

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

62  

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

71  

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

74  

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

76  

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

87  

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

89  

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

91  

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

99  

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

107  

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

114  

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

116  

116  
        Transfers up to `buffer_size( buffers )` bytes from the internal
117  
        Transfers up to `buffer_size( buffers )` bytes from the internal
117 -
        buffer to the provided mutable buffer sequence, filling buffers
118 +
        buffer to the provided mutable buffer sequence. If no data
118 -
        completely before returning. If no data remains, returns
119 +
        remains, returns `error::eof`. Before every read, the attached
119 -
        `error::eof`. Before every read, the attached @ref fuse is
120 +
        @ref fuse is consulted to possibly inject an error for testing
120 -
        consulted to possibly inject an error for testing fault scenarios.
121 +
        fault scenarios.
121 -
        The returned `std::size_t` is the number of bytes transferred.
 
122  

122  

123 -
        @par Effects
123 +
        @param buffers The mutable buffer sequence to receive data.
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  

124  

128 -
        @par Exception Safety
125 +
        @return An awaitable yielding `(error_code,std::size_t)`.
129 -
        No-throw guarantee.
126 +

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

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

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

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

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

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

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

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

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

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

183  

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

185  

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

187  

135  
        @see fuse
188  
        @see fuse
136  
    */
189  
    */
137  
    template<MutableBufferSequence MB>
190  
    template<MutableBufferSequence MB>
138  
    auto
191  
    auto
139  
    read(MB buffers)
192  
    read(MB buffers)
140  
    {
193  
    {
141  
        struct awaitable
194  
        struct awaitable
142  
        {
195  
        {
143  
            read_source* self_;
196  
            read_source* self_;
144  
            MB buffers_;
197  
            MB buffers_;
145  

198  

146  
            bool await_ready() const noexcept { return true; }
199  
            bool await_ready() const noexcept { return true; }
147 -
            // This method is required to satisfy Capy's IoAwaitable concept,
 
148 -
            // but is never called because await_ready() returns true.
 
149 -
            //
 
150 -
            // Capy uses a two-layer awaitable system: the promise's
 
151 -
            // await_transform wraps awaitables in a transform_awaiter whose
 
152 -
            // standard await_suspend(coroutine_handle) calls this custom
 
153 -
            // 3-argument overload, passing the executor and stop_token from
 
154 -
            // the coroutine's context. For synchronous test awaitables like
 
155 -
            // this one, the coroutine never suspends, so this is not invoked.
 
156 -
            // The signature exists to allow the same awaitable type to work
 
157 -
            // with both synchronous (test) and asynchronous (real I/O) code.
 
158  

200  

159  
            void await_suspend(
201  
            void await_suspend(
160  
                coro,
202  
                coro,
161  
                executor_ref,
203  
                executor_ref,
162  
                std::stop_token) const noexcept
204  
                std::stop_token) const noexcept
163  
            {
205  
            {
164  
            }
206  
            }
165  

207  

166  
            io_result<std::size_t>
208  
            io_result<std::size_t>
167  
            await_resume()
209  
            await_resume()
168  
            {
210  
            {
169 -
                auto ec = self_->f_->maybe_fail();
211 +
                if(buffer_empty(buffers_))
 
212 +
                    return {{}, 0};
 
213 +

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

217  

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

220  

176 -
                if(avail > self_->max_read_size_)
 
177 -
                    avail = self_->max_read_size_;
 
178  
                std::size_t avail = self_->data_.size() - self_->pos_;
221  
                std::size_t avail = self_->data_.size() - self_->pos_;
179  
                auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
222  
                auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
180  
                std::size_t const n = buffer_copy(buffers_, src);
223  
                std::size_t const n = buffer_copy(buffers_, src);
181  
                self_->pos_ += n;
224  
                self_->pos_ += n;
 
225 +

 
226 +
                if(n < buffer_size(buffers_))
 
227 +
                    return {error::eof, n};
182  
                return {{}, n};
228  
                return {{}, n};
183  
            }
229  
            }
184  
        };
230  
        };
185  
        return awaitable{this, buffers};
231  
        return awaitable{this, buffers};
186  
    }
232  
    }
187  
};
233  
};
188  

234  

189  
} // test
235  
} // test
190  
} // capy
236  
} // capy
191  
} // boost
237  
} // boost
192  

238  

193  
#endif
239  
#endif