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

165  

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

172  

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

175  

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

188  

178  
} // test
189  
} // test
179  
} // capy
190  
} // capy
180  
} // boost
191  
} // boost
181  

192  

182  
#endif
193  
#endif