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_BUFFER_SOURCE_HPP
10  
#ifndef BOOST_CAPY_TEST_BUFFER_SOURCE_HPP
11  
#define BOOST_CAPY_TEST_BUFFER_SOURCE_HPP
11  
#define BOOST_CAPY_TEST_BUFFER_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/make_buffer.hpp>
15  
#include <boost/capy/buffers/make_buffer.hpp>
16  
#include <boost/capy/coro.hpp>
16  
#include <boost/capy/coro.hpp>
17  
#include <boost/capy/ex/executor_ref.hpp>
17  
#include <boost/capy/ex/executor_ref.hpp>
18  
#include <boost/capy/io_result.hpp>
18  
#include <boost/capy/io_result.hpp>
19  
#include <boost/capy/test/fuse.hpp>
19  
#include <boost/capy/test/fuse.hpp>
20  

20  

21  
#include <algorithm>
21  
#include <algorithm>
22  
#include <stop_token>
22  
#include <stop_token>
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 buffer source for testing push operations.
30  
/** A mock buffer source for testing push operations.
31  

31  

32  
    Use this to verify code that transfers data from a buffer source to
32  
    Use this to verify code that transfers data from a buffer source to
33  
    a sink without needing real I/O. Call @ref provide to supply data,
33  
    a sink without needing real I/O. Call @ref provide to supply data,
34  
    then @ref pull to retrieve buffer descriptors. The associated
34  
    then @ref pull to retrieve buffer descriptors. The associated
35  
    @ref fuse enables error injection at controlled points.
35  
    @ref fuse enables error injection at controlled points.
36  

36  

37  
    This class satisfies the @ref BufferSource concept by providing
37  
    This class satisfies the @ref BufferSource concept by providing
38  
    a pull interface that fills an array of buffer descriptors and
38  
    a pull interface that fills an array of buffer descriptors and
39  
    a consume interface to indicate bytes used.
39  
    a consume interface to indicate bytes used.
40  

40  

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

43  

44  
    @par Example
44  
    @par Example
45  
    @code
45  
    @code
46  
    fuse f;
46  
    fuse f;
47  
    buffer_source bs( f );
47  
    buffer_source bs( f );
48  
    bs.provide( "Hello, " );
48  
    bs.provide( "Hello, " );
49  
    bs.provide( "World!" );
49  
    bs.provide( "World!" );
50  

50  

51  
    auto r = f.armed( [&]( fuse& ) -> task<void> {
51  
    auto r = f.armed( [&]( fuse& ) -> task<void> {
52  
        const_buffer arr[16];
52  
        const_buffer arr[16];
53  
        auto [ec, count] = co_await bs.pull( arr, 16 );
53  
        auto [ec, count] = co_await bs.pull( arr, 16 );
54  
        if( ec )
54  
        if( ec )
55  
            co_return;
55  
            co_return;
56  
        // arr[0..count) contains buffer descriptors
56  
        // arr[0..count) contains buffer descriptors
57  
        std::size_t n = buffer_size( std::span( arr, count ) );
57  
        std::size_t n = buffer_size( std::span( arr, count ) );
58  
        bs.consume( n );
58  
        bs.consume( n );
59  
    } );
59  
    } );
60  
    @endcode
60  
    @endcode
61  

61  

62  
    @see fuse, BufferSource
62  
    @see fuse, BufferSource
63  
*/
63  
*/
64  
class buffer_source
64  
class buffer_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_pull_size_;
69  
    std::size_t max_pull_size_;
70  

70  

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

73  

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

75  

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

86  

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

88  

89  
        Multiple calls accumulate data that @ref pull returns.
89  
        Multiple calls accumulate data that @ref pull 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 pulling.
107  
    /// Return the number of bytes available for pulling.
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  
    /** Consume bytes from the source.
114  
    /** Consume bytes from the source.
115  

115  

116  
        Advances the internal read position by the specified number
116  
        Advances the internal read position by the specified number
117  
        of bytes. The next call to @ref pull returns data starting
117  
        of bytes. The next call to @ref pull returns data starting
118  
        after the consumed bytes.
118  
        after the consumed bytes.
119  

119  

120  
        @param n The number of bytes to consume. Must not exceed the
120  
        @param n The number of bytes to consume. Must not exceed the
121  
        total size of buffers returned by the previous @ref pull.
121  
        total size of buffers returned by the previous @ref pull.
122  
    */
122  
    */
123  
    void
123  
    void
124  
    consume(std::size_t n) noexcept
124  
    consume(std::size_t n) noexcept
125  
    {
125  
    {
126  
        pos_ += n;
126  
        pos_ += n;
127  
    }
127  
    }
128  

128  

129  
    /** Pull buffer data from the source.
129  
    /** Pull buffer data from the source.
130  

130  

131  
        Fills the provided span with buffer descriptors pointing to
131  
        Fills the provided span with buffer descriptors pointing to
132  
        internal data starting from the current unconsumed position.
132  
        internal data starting from the current unconsumed position.
133  
        Returns a span of filled buffers. When no data remains,
133  
        Returns a span of filled buffers. When no data remains,
134  
        returns an empty span to signal completion.
134  
        returns an empty span to signal completion.
135  

135  

136  
        Calling pull multiple times without intervening @ref consume
136  
        Calling pull multiple times without intervening @ref consume
137  
        returns the same data. Use consume to advance past processed
137  
        returns the same data. Use consume to advance past processed
138  
        bytes.
138  
        bytes.
139  

139  

140  
        @param dest Span of const_buffer to fill.
140  
        @param dest Span of const_buffer to fill.
141  

141  

142  
        @return An awaitable yielding `(error_code,std::span<const_buffer>)`.
142  
        @return An awaitable yielding `(error_code,std::span<const_buffer>)`.
143  

143  

144  
        @see consume, fuse
144  
        @see consume, fuse
145  
    */
145  
    */
146  
    auto
146  
    auto
147  
    pull(std::span<const_buffer> dest)
147  
    pull(std::span<const_buffer> dest)
148  
    {
148  
    {
149  
        struct awaitable
149  
        struct awaitable
150  
        {
150  
        {
151  
            buffer_source* self_;
151  
            buffer_source* self_;
152  
            std::span<const_buffer> dest_;
152  
            std::span<const_buffer> dest_;
153  

153  

154  
            bool await_ready() const noexcept { return true; }
154  
            bool await_ready() const noexcept { return true; }
155  

155  

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

173  

163  
            io_result<std::span<const_buffer>>
174  
            io_result<std::span<const_buffer>>
164  
            await_resume()
175  
            await_resume()
165  
            {
176  
            {
166  
                auto ec = self_->f_->maybe_fail();
177  
                auto ec = self_->f_->maybe_fail();
167  
                if(ec)
178  
                if(ec)
168  
                    return {ec, {}};
179  
                    return {ec, {}};
169  

180  

170  
                if(self_->pos_ >= self_->data_.size())
181  
                if(self_->pos_ >= self_->data_.size())
171  
                    return {{}, {}}; // Source exhausted
182  
                    return {{}, {}}; // Source exhausted
172  

183  

173  
                std::size_t avail = self_->data_.size() - self_->pos_;
184  
                std::size_t avail = self_->data_.size() - self_->pos_;
174  
                std::size_t to_return = (std::min)(avail, self_->max_pull_size_);
185  
                std::size_t to_return = (std::min)(avail, self_->max_pull_size_);
175  

186  

176  
                if(dest_.empty())
187  
                if(dest_.empty())
177  
                    return {{}, {}};
188  
                    return {{}, {}};
178  

189  

179  
                // Fill a single buffer descriptor
190  
                // Fill a single buffer descriptor
180  
                dest_[0] = make_buffer(
191  
                dest_[0] = make_buffer(
181  
                    self_->data_.data() + self_->pos_,
192  
                    self_->data_.data() + self_->pos_,
182  
                    to_return);
193  
                    to_return);
183  

194  

184  
                return {{}, dest_.first(1)};
195  
                return {{}, dest_.first(1)};
185  
            }
196  
            }
186  
        };
197  
        };
187  
        return awaitable{this, dest};
198  
        return awaitable{this, dest};
188  
    }
199  
    }
189  
};
200  
};
190  

201  

191  
} // test
202  
} // test
192  
} // capy
203  
} // capy
193  
} // boost
204  
} // boost
194  

205  

195  
#endif
206  
#endif