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 <coroutine>
17  
#include <coroutine>
18  
#include <boost/capy/ex/io_env.hpp>
18  
#include <boost/capy/ex/io_env.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 <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 stream for testing write operations.
31  
/** A mock stream for testing write operations.
32  

32  

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

39  

40  
    This class satisfies the @ref WriteStream concept.
40  
    This class satisfies the @ref WriteStream concept.
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  
    write_stream ws( f );
48  
    write_stream ws( f );
49  

49  

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

58  

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

67  

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

81  

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

84  

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

86  

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

97  

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

104  

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

106  

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

110  

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

112  

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

121  

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

128  

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

130  

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

136  

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

141  

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

144  

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

146  

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

148  

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

159  

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

161  

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

178  

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

184  

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

188  

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

191  

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

196  

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

203  

204  
                return {{}, n};
204  
                return {{}, n};
205  
            }
205  
            }
206  
        };
206  
        };
207  
        return awaitable{this, buffers};
207  
        return awaitable{this, buffers};
208  
    }
208  
    }
209  
};
209  
};
210  

210  

211  
} // test
211  
} // test
212  
} // capy
212  
} // capy
213  
} // boost
213  
} // boost
214  

214  

215  
#endif
215  
#endif