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  
            void await_suspend(
148  
            void await_suspend(
149  
                coro,
149  
                coro,
150  
                executor_ref,
150  
                executor_ref,
151  
                std::stop_token) const noexcept
151  
                std::stop_token) const noexcept
152  
            {
152  
            {
153  
            }
153  
            }
154  

154  

155  
            io_result<std::size_t>
155  
            io_result<std::size_t>
156  
            await_resume()
156  
            await_resume()
157  
            {
157  
            {
158  
                auto ec = self_->f_->maybe_fail();
158  
                auto ec = self_->f_->maybe_fail();
159  
                if(ec)
159  
                if(ec)
160  
                    return {ec, 0};
160  
                    return {ec, 0};
161  

161  

162  
                if(self_->pos_ >= self_->data_.size())
162  
                if(self_->pos_ >= self_->data_.size())
163  
                    return {error::eof, 0};
163  
                    return {error::eof, 0};
164  

164  

165  
                std::size_t avail = self_->data_.size() - self_->pos_;
165  
                std::size_t avail = self_->data_.size() - self_->pos_;
166  
                if(avail > self_->max_read_size_)
166  
                if(avail > self_->max_read_size_)
167  
                    avail = self_->max_read_size_;
167  
                    avail = self_->max_read_size_;
168  
                auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
168  
                auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
169  
                std::size_t const n = buffer_copy(buffers_, src);
169  
                std::size_t const n = buffer_copy(buffers_, src);
170  
                self_->pos_ += n;
170  
                self_->pos_ += n;
171  
                return {{}, n};
171  
                return {{}, n};
172  
            }
172  
            }
173  
        };
173  
        };
174  
        return awaitable{this, buffers};
174  
        return awaitable{this, buffers};
175  
    }
175  
    }
176  
};
176  
};
177  

177  

178  
} // test
178  
} // test
179  
} // capy
179  
} // capy
180  
} // boost
180  
} // boost
181  

181  

182  
#endif
182  
#endif