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  
    Unlike @ref read_stream which provides partial reads via `read_some`,
39  
    this class satisfies the @ref ReadSource concept by providing complete
39  
    this class satisfies the @ref ReadSource concept by providing complete
40  
    reads that fill the entire buffer sequence before returning.
40  
    reads that fill the entire buffer sequence 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_(&f)
82  
        : f_(&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 data from the source.
114  
    /** Asynchronously read 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, filling buffers
117  
        buffer to the provided mutable buffer sequence, filling buffers
118  
        completely before returning. If no data remains, returns
118  
        completely before returning. If no data remains, returns
119  
        `error::eof`. Before every read, the attached @ref fuse is
119  
        `error::eof`. Before every read, the attached @ref fuse is
120  
        consulted to possibly inject an error for testing fault scenarios.
120  
        consulted to possibly inject an error for testing fault scenarios.
121  
        The returned `std::size_t` is the number of bytes transferred.
121  
        The returned `std::size_t` is the number of bytes transferred.
122  

122  

123  
        @par Effects
123  
        @par Effects
124  
        On success, advances the internal read position by the number of
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
125  
        bytes copied. If an error is injected by the fuse, the read position
126  
        remains unchanged.
126  
        remains unchanged.
127  

127  

128  
        @par Exception Safety
128  
        @par Exception Safety
129  
        No-throw guarantee.
129  
        No-throw guarantee.
130  

130  

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

132  

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

134  

135  
        @see fuse
135  
        @see fuse
136  
    */
136  
    */
137  
    template<MutableBufferSequence MB>
137  
    template<MutableBufferSequence MB>
138  
    auto
138  
    auto
139  
    read(MB buffers)
139  
    read(MB buffers)
140  
    {
140  
    {
141  
        struct awaitable
141  
        struct awaitable
142  
        {
142  
        {
143  
            read_source* self_;
143  
            read_source* self_;
144  
            MB buffers_;
144  
            MB buffers_;
145  

145  

146  
            bool await_ready() const noexcept { return true; }
146  
            bool await_ready() const noexcept { return true; }
147  

147  

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

165  

166  
            io_result<std::size_t>
166  
            io_result<std::size_t>
167  
            await_resume()
167  
            await_resume()
168  
            {
168  
            {
169  
                auto ec = self_->f_->maybe_fail();
169  
                auto ec = self_->f_->maybe_fail();
170  
                if(ec)
170  
                if(ec)
171  
                    return {ec, 0};
171  
                    return {ec, 0};
172  

172  

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

175  

176  
                std::size_t avail = self_->data_.size() - self_->pos_;
176  
                std::size_t avail = self_->data_.size() - self_->pos_;
177  
                if(avail > self_->max_read_size_)
177  
                if(avail > self_->max_read_size_)
178  
                    avail = self_->max_read_size_;
178  
                    avail = self_->max_read_size_;
179  
                auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
179  
                auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
180  
                std::size_t const n = buffer_copy(buffers_, src);
180  
                std::size_t const n = buffer_copy(buffers_, src);
181  
                self_->pos_ += n;
181  
                self_->pos_ += n;
182  
                return {{}, n};
182  
                return {{}, n};
183  
            }
183  
            }
184  
        };
184  
        };
185  
        return awaitable{this, buffers};
185  
        return awaitable{this, buffers};
186  
    }
186  
    }
187  
};
187  
};
188  

188  

189  
} // test
189  
} // test
190  
} // capy
190  
} // capy
191  
} // boost
191  
} // boost
192  

192  

193  
#endif
193  
#endif