Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : //
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)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_TEST_WRITE_STREAM_HPP
11 : #define BOOST_CAPY_TEST_WRITE_STREAM_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/buffers.hpp>
15 : #include <boost/capy/buffers/buffer_copy.hpp>
16 : #include <boost/capy/buffers/make_buffer.hpp>
17 : #include <boost/capy/coro.hpp>
18 : #include <boost/capy/ex/executor_ref.hpp>
19 : #include <boost/capy/io_result.hpp>
20 : #include <boost/capy/error.hpp>
21 : #include <boost/capy/test/fuse.hpp>
22 :
23 : #include <algorithm>
24 : #include <stop_token>
25 : #include <string>
26 : #include <string_view>
27 :
28 : namespace boost {
29 : namespace capy {
30 : namespace test {
31 :
32 : /** A mock stream for testing write operations.
33 :
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
36 : or @ref data to retrieve what was written. The associated
37 : @ref fuse enables error injection at controlled points. An
38 : optional `max_write_size` constructor parameter limits bytes
39 : per write to simulate chunked delivery.
40 :
41 : @par Thread Safety
42 : Not thread-safe.
43 :
44 : @par Example
45 : @code
46 : fuse f;
47 : write_stream ws( f );
48 :
49 : auto r = f.armed( [&]( fuse& ) -> task<void> {
50 : auto [ec, n] = co_await ws.write_some(
51 : const_buffer( "Hello", 5 ) );
52 : if( ec )
53 : co_return;
54 : // ws.str() returns "Hello"
55 : } );
56 : @endcode
57 :
58 : @see fuse
59 : */
60 : class write_stream
61 : {
62 : fuse* f_;
63 : std::string data_;
64 : std::string expect_;
65 : std::size_t max_write_size_;
66 :
67 : std::error_code
68 834 : consume_match_() noexcept
69 : {
70 834 : if(data_.empty() || expect_.empty())
71 834 : return {};
72 0 : std::size_t const n = (std::min)(data_.size(), expect_.size());
73 0 : if(std::string_view(data_.data(), n) !=
74 0 : std::string_view(expect_.data(), n))
75 0 : return error::test_failure;
76 0 : data_.erase(0, n);
77 0 : expect_.erase(0, n);
78 0 : return {};
79 : }
80 :
81 : public:
82 : /** Construct a write stream.
83 :
84 : @param f The fuse used to inject errors during writes.
85 :
86 : @param max_write_size Maximum bytes transferred per write.
87 : Use to simulate chunked network delivery.
88 : */
89 1014 : explicit write_stream(
90 : fuse& f,
91 : std::size_t max_write_size = std::size_t(-1)) noexcept
92 1014 : : f_(&f)
93 1014 : , max_write_size_(max_write_size)
94 : {
95 1014 : }
96 :
97 : /// Return the written data as a string view.
98 : std::string_view
99 898 : data() const noexcept
100 : {
101 898 : return data_;
102 : }
103 :
104 : /** Set the expected data for subsequent writes.
105 :
106 : Stores the expected data and immediately tries to match
107 : against any data already written. Matched data is consumed
108 : from both buffers.
109 :
110 : @param sv The expected data.
111 :
112 : @return An error if existing data does not match.
113 : */
114 : std::error_code
115 : expect(std::string_view sv)
116 : {
117 : expect_.assign(sv);
118 : return consume_match_();
119 : }
120 :
121 : /// Return the number of bytes written.
122 : std::size_t
123 2 : size() const noexcept
124 : {
125 2 : return data_.size();
126 : }
127 :
128 : /** Asynchronously write data to the stream.
129 :
130 : Transfers up to `buffer_size( buffers )` bytes from the provided
131 : const buffer sequence to the internal buffer. Before every write,
132 : the attached @ref fuse is consulted to possibly inject an error
133 : for testing fault scenarios. The returned `std::size_t` is the
134 : number of bytes transferred.
135 :
136 : @par Effects
137 : On success, appends the written bytes to the internal buffer.
138 : If an error is injected by the fuse, the internal buffer remains
139 : unchanged.
140 :
141 : @par Exception Safety
142 : No-throw guarantee.
143 :
144 : @param buffers The const buffer sequence containing data to write.
145 :
146 : @return An awaitable yielding ( error_code, std::size_t ).
147 :
148 : @see fuse
149 : */
150 : template<ConstBufferSequence CB>
151 : auto
152 982 : write_some(CB buffers)
153 : {
154 : struct awaitable
155 : {
156 : write_stream* self_;
157 : CB buffers_;
158 :
159 982 : bool await_ready() const noexcept { return true; }
160 :
161 0 : void await_suspend(
162 : coro,
163 : executor_ref,
164 : std::stop_token) const noexcept
165 : {
166 0 : }
167 :
168 : io_result<std::size_t>
169 982 : await_resume()
170 : {
171 982 : auto ec = self_->f_->maybe_fail();
172 908 : if(ec)
173 74 : return {ec, 0};
174 :
175 834 : std::size_t n = buffer_size(buffers_);
176 834 : n = (std::min)(n, self_->max_write_size_);
177 834 : if(n == 0)
178 0 : return {{}, 0};
179 :
180 834 : std::size_t const old_size = self_->data_.size();
181 834 : self_->data_.resize(old_size + n);
182 834 : buffer_copy(make_buffer(
183 834 : self_->data_.data() + old_size, n), buffers_, n);
184 :
185 834 : ec = self_->consume_match_();
186 834 : if(ec)
187 0 : return {ec, n};
188 :
189 834 : return {{}, n};
190 : }
191 : };
192 982 : return awaitable{this, buffers};
193 : }
194 : };
195 :
196 : } // test
197 : } // capy
198 : } // boost
199 :
200 : #endif
|