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_WRITE_STREAM_HPP
10  
#ifndef BOOST_CAPY_TEST_WRITE_STREAM_HPP
11  
#define BOOST_CAPY_TEST_WRITE_STREAM_HPP
11  
#define BOOST_CAPY_TEST_WRITE_STREAM_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 <algorithm>
23  
#include <algorithm>
24  
#include <stop_token>
24  
#include <stop_token>
25  
#include <string>
25  
#include <string>
26  
#include <string_view>
26  
#include <string_view>
27  

27  

28  
namespace boost {
28  
namespace boost {
29  
namespace capy {
29  
namespace capy {
30  
namespace test {
30  
namespace test {
31  

31  

32  
/** A mock stream for testing write operations.
32  
/** A mock stream for testing write operations.
33  

33  

34  
    Use this to verify code that performs writes without needing
34  
    Use this to verify code that performs writes without needing
35  
    real I/O. Call @ref write_some to write data, then @ref data
35  
    real I/O. Call @ref write_some to write data, then @ref data
36  
    to retrieve what was written. The associated @ref fuse enables
36  
    to retrieve what was written. The associated @ref fuse enables
37  
    error injection at controlled points. An optional
37  
    error injection at controlled points. An optional
38  
    `max_write_size` constructor parameter limits bytes per write
38  
    `max_write_size` constructor parameter limits bytes per write
39  
    to simulate chunked delivery.
39  
    to simulate chunked delivery.
40  

40  

41  
    This class satisfies the @ref WriteStream concept.
41  
    This class satisfies the @ref WriteStream concept.
42  

42  

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

45  

46  
    @par Example
46  
    @par Example
47  
    @code
47  
    @code
48  
    fuse f;
48  
    fuse f;
49  
    write_stream ws( f );
49  
    write_stream ws( f );
50  

50  

51  
    auto r = f.armed( [&]( fuse& ) -> task<void> {
51  
    auto r = f.armed( [&]( fuse& ) -> task<void> {
52  
        auto [ec, n] = co_await ws.write_some(
52  
        auto [ec, n] = co_await ws.write_some(
53  
            const_buffer( "Hello", 5 ) );
53  
            const_buffer( "Hello", 5 ) );
54  
        if( ec )
54  
        if( ec )
55  
            co_return;
55  
            co_return;
56  
        // ws.data() returns "Hello"
56  
        // ws.data() returns "Hello"
57  
    } );
57  
    } );
58  
    @endcode
58  
    @endcode
59  

59  

60  
    @see fuse, WriteStream
60  
    @see fuse, WriteStream
61  
*/
61  
*/
62  
class write_stream
62  
class write_stream
63  
{
63  
{
64  
    fuse f_;
64  
    fuse f_;
65  
    std::string data_;
65  
    std::string data_;
66  
    std::string expect_;
66  
    std::string expect_;
67  
    std::size_t max_write_size_;
67  
    std::size_t max_write_size_;
68  

68  

69  
    std::error_code
69  
    std::error_code
70  
    consume_match_() noexcept
70  
    consume_match_() noexcept
71  
    {
71  
    {
72  
        if(data_.empty() || expect_.empty())
72  
        if(data_.empty() || expect_.empty())
73  
            return {};
73  
            return {};
74  
        std::size_t const n = (std::min)(data_.size(), expect_.size());
74  
        std::size_t const n = (std::min)(data_.size(), expect_.size());
75  
        if(std::string_view(data_.data(), n) !=
75  
        if(std::string_view(data_.data(), n) !=
76  
            std::string_view(expect_.data(), n))
76  
            std::string_view(expect_.data(), n))
77  
            return error::test_failure;
77  
            return error::test_failure;
78  
        data_.erase(0, n);
78  
        data_.erase(0, n);
79  
        expect_.erase(0, n);
79  
        expect_.erase(0, n);
80  
        return {};
80  
        return {};
81  
    }
81  
    }
82  

82  

83  
public:
83  
public:
84  
    /** Construct a write stream.
84  
    /** Construct a write stream.
85  

85  

86  
        @param f The fuse used to inject errors during writes.
86  
        @param f The fuse used to inject errors during writes.
87  

87  

88  
        @param max_write_size Maximum bytes transferred per write.
88  
        @param max_write_size Maximum bytes transferred per write.
89  
        Use to simulate chunked network delivery.
89  
        Use to simulate chunked network delivery.
90  
    */
90  
    */
91  
    explicit write_stream(
91  
    explicit write_stream(
92  
        fuse f = {},
92  
        fuse f = {},
93  
        std::size_t max_write_size = std::size_t(-1)) noexcept
93  
        std::size_t max_write_size = std::size_t(-1)) noexcept
94  
        : f_(std::move(f))
94  
        : f_(std::move(f))
95  
        , max_write_size_(max_write_size)
95  
        , max_write_size_(max_write_size)
96  
    {
96  
    {
97  
    }
97  
    }
98  

98  

99  
    /// Return the written data as a string view.
99  
    /// Return the written data as a string view.
100  
    std::string_view
100  
    std::string_view
101  
    data() const noexcept
101  
    data() const noexcept
102  
    {
102  
    {
103  
        return data_;
103  
        return data_;
104  
    }
104  
    }
105  

105  

106  
    /** Set the expected data for subsequent writes.
106  
    /** Set the expected data for subsequent writes.
107  

107  

108  
        Stores the expected data and immediately tries to match
108  
        Stores the expected data and immediately tries to match
109  
        against any data already written. Matched data is consumed
109  
        against any data already written. Matched data is consumed
110  
        from both buffers.
110  
        from both buffers.
111  

111  

112  
        @param sv The expected data.
112  
        @param sv The expected data.
113  

113  

114  
        @return An error if existing data does not match.
114  
        @return An error if existing data does not match.
115  
    */
115  
    */
116  
    std::error_code
116  
    std::error_code
117  
    expect(std::string_view sv)
117  
    expect(std::string_view sv)
118  
    {
118  
    {
119  
        expect_.assign(sv);
119  
        expect_.assign(sv);
120  
        return consume_match_();
120  
        return consume_match_();
121  
    }
121  
    }
122  

122  

123  
    /// Return the number of bytes written.
123  
    /// Return the number of bytes written.
124  
    std::size_t
124  
    std::size_t
125  
    size() const noexcept
125  
    size() const noexcept
126  
    {
126  
    {
127  
        return data_.size();
127  
        return data_.size();
128  
    }
128  
    }
129  

129  

130  
    /** Asynchronously write data to the stream.
130  
    /** Asynchronously write data to the stream.
131  

131  

132  
        Transfers up to `buffer_size( buffers )` bytes from the provided
132  
        Transfers up to `buffer_size( buffers )` bytes from the provided
133  
        const buffer sequence to the internal buffer. Before every write,
133  
        const buffer sequence to the internal buffer. Before every write,
134  
        the attached @ref fuse is consulted to possibly inject an error
134  
        the attached @ref fuse is consulted to possibly inject an error
135  
        for testing fault scenarios. The returned `std::size_t` is the
135  
        for testing fault scenarios. The returned `std::size_t` is the
136  
        number of bytes transferred.
136  
        number of bytes transferred.
137  

137  

138  
        @par Effects
138  
        @par Effects
139  
        On success, appends the written bytes to the internal buffer.
139  
        On success, appends the written bytes to the internal buffer.
140  
        If an error is injected by the fuse, the internal buffer remains
140  
        If an error is injected by the fuse, the internal buffer remains
141  
        unchanged.
141  
        unchanged.
142  

142  

143  
        @par Exception Safety
143  
        @par Exception Safety
144  
        No-throw guarantee.
144  
        No-throw guarantee.
145  

145  

146  
        @param buffers The const buffer sequence containing data to write.
146  
        @param buffers The const buffer sequence containing data to write.
147  

147  

148  
        @return An awaitable yielding `(error_code,std::size_t)`.
148  
        @return An awaitable yielding `(error_code,std::size_t)`.
149  

149  

150  
        @see fuse
150  
        @see fuse
151  
    */
151  
    */
152  
    template<ConstBufferSequence CB>
152  
    template<ConstBufferSequence CB>
153  
    auto
153  
    auto
154  
    write_some(CB buffers)
154  
    write_some(CB buffers)
155  
    {
155  
    {
156  
        struct awaitable
156  
        struct awaitable
157  
        {
157  
        {
158  
            write_stream* self_;
158  
            write_stream* self_;
159  
            CB buffers_;
159  
            CB buffers_;
160  

160  

161  
            bool await_ready() const noexcept { return true; }
161  
            bool await_ready() const noexcept { return true; }
162  

162  

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

180  

181  
            io_result<std::size_t>
181  
            io_result<std::size_t>
182  
            await_resume()
182  
            await_resume()
183  
            {
183  
            {
184  
                if(buffer_empty(buffers_))
184  
                if(buffer_empty(buffers_))
185  
                    return {{}, 0};
185  
                    return {{}, 0};
186  

186  

187  
                auto ec = self_->f_.maybe_fail();
187  
                auto ec = self_->f_.maybe_fail();
188  
                if(ec)
188  
                if(ec)
189  
                    return {ec, 0};
189  
                    return {ec, 0};
190  

190  

191  
                std::size_t n = buffer_size(buffers_);
191  
                std::size_t n = buffer_size(buffers_);
192  
                n = (std::min)(n, self_->max_write_size_);
192  
                n = (std::min)(n, self_->max_write_size_);
193  

193  

194  
                std::size_t const old_size = self_->data_.size();
194  
                std::size_t const old_size = self_->data_.size();
195  
                self_->data_.resize(old_size + n);
195  
                self_->data_.resize(old_size + n);
196  
                buffer_copy(make_buffer(
196  
                buffer_copy(make_buffer(
197  
                    self_->data_.data() + old_size, n), buffers_, n);
197  
                    self_->data_.data() + old_size, n), buffers_, n);
198  

198  

199  
                ec = self_->consume_match_();
199  
                ec = self_->consume_match_();
200  
                if(ec)
200  
                if(ec)
201  
                {
201  
                {
202  
                    self_->data_.resize(old_size);
202  
                    self_->data_.resize(old_size);
203  
                    return {ec, 0};
203  
                    return {ec, 0};
204  
                }
204  
                }
205  

205  

206  
                return {{}, n};
206  
                return {{}, n};
207  
            }
207  
            }
208  
        };
208  
        };
209  
        return awaitable{this, buffers};
209  
        return awaitable{this, buffers};
210  
    }
210  
    }
211  
};
211  
};
212  

212  

213  
} // test
213  
} // test
214  
} // capy
214  
} // capy
215  
} // boost
215  
} // boost
216  

216  

217  
#endif
217  
#endif