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 str
35  
    real I/O. Call @ref write_some to write data, then @ref str
36  
    or @ref data to retrieve what was written. The associated
36  
    or @ref data to retrieve what was written. The associated
37  
    @ref fuse enables error injection at controlled points. An
37  
    @ref fuse enables error injection at controlled points. An
38  
    optional `max_write_size` constructor parameter limits bytes
38  
    optional `max_write_size` constructor parameter limits bytes
39  
    per write to simulate chunked delivery.
39  
    per write to simulate chunked delivery.
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  
    write_stream ws( f );
47  
    write_stream ws( f );
48  

48  

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

57  

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

66  

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

80  

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

83  

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

85  

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

96  

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

103  

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

105  

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

109  

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

111  

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

120  

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

127  

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

129  

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

135  

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

140  

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

143  

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

145  

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

147  

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

158  

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

160  

161  
            void await_suspend(
161  
            void await_suspend(
162  
                coro,
162  
                coro,
163  
                executor_ref,
163  
                executor_ref,
164  
                std::stop_token) const noexcept
164  
                std::stop_token) const noexcept
165  
            {
165  
            {
166  
            }
166  
            }
167  

167  

168  
            io_result<std::size_t>
168  
            io_result<std::size_t>
169  
            await_resume()
169  
            await_resume()
170  
            {
170  
            {
171  
                auto ec = self_->f_->maybe_fail();
171  
                auto ec = self_->f_->maybe_fail();
172  
                if(ec)
172  
                if(ec)
173  
                    return {ec, 0};
173  
                    return {ec, 0};
174  

174  

175  
                std::size_t n = buffer_size(buffers_);
175  
                std::size_t n = buffer_size(buffers_);
176  
                n = (std::min)(n, self_->max_write_size_);
176  
                n = (std::min)(n, self_->max_write_size_);
177  
                if(n == 0)
177  
                if(n == 0)
178  
                    return {{}, 0};
178  
                    return {{}, 0};
179  

179  

180  
                std::size_t const old_size = self_->data_.size();
180  
                std::size_t const old_size = self_->data_.size();
181  
                self_->data_.resize(old_size + n);
181  
                self_->data_.resize(old_size + n);
182  
                buffer_copy(make_buffer(
182  
                buffer_copy(make_buffer(
183  
                    self_->data_.data() + old_size, n), buffers_, n);
183  
                    self_->data_.data() + old_size, n), buffers_, n);
184  

184  

185  
                ec = self_->consume_match_();
185  
                ec = self_->consume_match_();
186  
                if(ec)
186  
                if(ec)
187  
                    return {ec, n};
187  
                    return {ec, n};
188  

188  

189  
                return {{}, n};
189  
                return {{}, n};
190  
            }
190  
            }
191  
        };
191  
        };
192  
        return awaitable{this, buffers};
192  
        return awaitable{this, buffers};
193  
    }
193  
    }
194  
};
194  
};
195  

195  

196  
} // test
196  
} // test
197  
} // capy
197  
} // capy
198  
} // boost
198  
} // boost
199  

199  

200  
#endif
200  
#endif