100.00% Lines (46/46) 100.00% Functions (9/9)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // Copyright (c) 2026 Michael Vandeberg 3   // Copyright (c) 2026 Michael Vandeberg
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
9   // 9   //
10   10  
11   #ifndef BOOST_CAPY_TEST_WRITE_STREAM_HPP 11   #ifndef BOOST_CAPY_TEST_WRITE_STREAM_HPP
12   #define BOOST_CAPY_TEST_WRITE_STREAM_HPP 12   #define BOOST_CAPY_TEST_WRITE_STREAM_HPP
13   13  
14   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/buffers.hpp> 15   #include <boost/capy/buffers.hpp>
16   #include <boost/capy/buffers/buffer_copy.hpp> 16   #include <boost/capy/buffers/buffer_copy.hpp>
17   #include <boost/capy/buffers/make_buffer.hpp> 17   #include <boost/capy/buffers/make_buffer.hpp>
18   #include <coroutine> 18   #include <coroutine>
19   #include <boost/capy/ex/io_env.hpp> 19   #include <boost/capy/ex/io_env.hpp>
20   #include <boost/capy/io_result.hpp> 20   #include <boost/capy/io_result.hpp>
21   #include <boost/capy/error.hpp> 21   #include <boost/capy/error.hpp>
22   #include <boost/capy/test/fuse.hpp> 22   #include <boost/capy/test/fuse.hpp>
23   23  
24   #include <algorithm> 24   #include <algorithm>
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   /** Captures bytes passed to `write_some`, retrievable afterward through `data`. 32   /** Captures bytes passed to `write_some`, retrievable afterward through `data`.
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 + @par !example example
48 - fuse f;  
49 -  
50 - auto r = f.armed( [&]( fuse& ) -> task<void> {  
51 - // Constructed inside the lambda: armed() re-invokes this  
52 - // function once per injected failure point, and a write_stream  
53 - // constructed outside would carry accumulated data across  
54 - // those rounds.  
55 - write_stream ws( f );  
56 - auto [ec, n] = co_await ws.write_some(  
57 - const_buffer( "Hello", 5 ) );  
58 - if( ec )  
59 - co_return;  
60 - // ws.data() returns "Hello"  
61 - } );  
62 - @endcode  
63   48  
64   49  
65   @see fuse, WriteStream 50   @see fuse, WriteStream
66   */ 51   */
67   class write_stream 52   class write_stream
68   { 53   {
69   fuse f_; 54   fuse f_;
70   std::string data_; 55   std::string data_;
71   std::string expect_; 56   std::string expect_;
72   std::size_t max_write_size_; 57   std::size_t max_write_size_;
73   58  
74   std::error_code 59   std::error_code
HITCBC 75   364 consume_match_() noexcept 60   364 consume_match_() noexcept
76   { 61   {
HITCBC 77   364 if(data_.empty() || expect_.empty()) 62   364 if(data_.empty() || expect_.empty())
HITCBC 78   347 return {}; 63   347 return {};
HITCBC 79   17 std::size_t const n = (std::min)(data_.size(), expect_.size()); 64   17 std::size_t const n = (std::min)(data_.size(), expect_.size());
HITCBC 80   17 if(std::string_view(data_.data(), n) != 65   17 if(std::string_view(data_.data(), n) !=
HITCBC 81   34 std::string_view(expect_.data(), n)) 66   34 std::string_view(expect_.data(), n))
HITCBC 82   4 return error::test_failure; 67   4 return error::test_failure;
HITCBC 83   13 data_.erase(0, n); 68   13 data_.erase(0, n);
HITCBC 84   13 expect_.erase(0, n); 69   13 expect_.erase(0, n);
HITCBC 85   13 return {}; 70   13 return {};
86   } 71   }
87   72  
88   public: 73   public:
89   /** Construct a write stream. 74   /** Construct a write stream.
90   75  
91   @param f The fuse used to inject errors during writes. 76   @param f The fuse used to inject errors during writes.
92   77  
93   @param max_write_size Maximum bytes transferred per write. 78   @param max_write_size Maximum bytes transferred per write.
94   Use to simulate chunked network delivery. 79   Use to simulate chunked network delivery.
95   */ 80   */
HITCBC 96   417 explicit write_stream( 81   417 explicit write_stream(
97   fuse f = {}, 82   fuse f = {},
98   std::size_t max_write_size = std::size_t(-1)) noexcept 83   std::size_t max_write_size = std::size_t(-1)) noexcept
HITCBC 99   417 : f_(std::move(f)) 84   417 : f_(std::move(f))
HITCBC 100   417 , max_write_size_(max_write_size) 85   417 , max_write_size_(max_write_size)
101   { 86   {
HITCBC 102   417 } 87   417 }
103   88  
104   /** Return the written data as a string view. 89   /** Return the written data as a string view.
105   90  
106   @return A view of bytes written but not yet matched by @ref expect. 91   @return A view of bytes written but not yet matched by @ref expect.
107   */ 92   */
108   std::string_view 93   std::string_view
HITCBC 109   314 data() const noexcept 94   314 data() const noexcept
110   { 95   {
HITCBC 111   314 return data_; 96   314 return data_;
112   } 97   }
113   98  
114   /** Set the expected data for subsequent writes. 99   /** Set the expected data for subsequent writes.
115   100  
116   Stores the expected data and immediately tries to match 101   Stores the expected data and immediately tries to match
117   against any data already written. Matched data is consumed 102   against any data already written. Matched data is consumed
118   from both buffers. 103   from both buffers.
119   104  
120   @param sv The expected data. 105   @param sv The expected data.
121   106  
122   @return An error if existing data does not match. 107   @return An error if existing data does not match.
123   */ 108   */
124   std::error_code 109   std::error_code
HITCBC 125   31 expect(std::string_view sv) 110   31 expect(std::string_view sv)
126   { 111   {
HITCBC 127   31 expect_.assign(sv); 112   31 expect_.assign(sv);
HITCBC 128   31 return consume_match_(); 113   31 return consume_match_();
129   } 114   }
130   115  
131   /** Return the number of bytes written. 116   /** Return the number of bytes written.
132   117  
133   @return The number of bytes written but not yet matched by @ref expect. 118   @return The number of bytes written but not yet matched by @ref expect.
134   */ 119   */
135   std::size_t 120   std::size_t
HITCBC 136   5 size() const noexcept 121   5 size() const noexcept
137   { 122   {
HITCBC 138   5 return data_.size(); 123   5 return data_.size();
139   } 124   }
140   125  
141   /** Asynchronously write data to the stream. 126   /** Asynchronously write data to the stream.
142   127  
143   Transfers up to `buffer_size( buffers )` bytes from the provided 128   Transfers up to `buffer_size( buffers )` bytes from the provided
144   const buffer sequence to the internal buffer. Before every write, 129   const buffer sequence to the internal buffer. Before every write,
145   the attached @ref fuse is consulted to possibly inject an error 130   the attached @ref fuse is consulted to possibly inject an error
146   for testing fault scenarios. The returned `std::size_t` is the 131   for testing fault scenarios. The returned `std::size_t` is the
147   number of bytes transferred. 132   number of bytes transferred.
148   133  
149   @par Effects 134   @par Effects
150   On success, appends the written bytes to the internal buffer. 135   On success, appends the written bytes to the internal buffer.
151   If an error is injected by the fuse, the internal buffer remains 136   If an error is injected by the fuse, the internal buffer remains
152   unchanged. 137   unchanged.
153   138  
154   @par Exception Safety 139   @par Exception Safety
155   Injected I/O conditions are reported via the `error_code` 140   Injected I/O conditions are reported via the `error_code`
156   component of the result. Throws `std::system_error` only when 141   component of the result. Throws `std::system_error` only when
157   the attached @ref fuse is in exception mode and reaches its 142   the attached @ref fuse is in exception mode and reaches its
158   failure point; no-throw otherwise. 143   failure point; no-throw otherwise.
159   144  
160   @par Cancellation 145   @par Cancellation
161   If the environment's stop token is requested, the write 146   If the environment's stop token is requested, the write
162   completes immediately with `error::canceled` and transfers no 147   completes immediately with `error::canceled` and transfers no
163   data. An empty buffer sequence is a no-op that completes 148   data. An empty buffer sequence is a no-op that completes
164   successfully regardless of the stop token. 149   successfully regardless of the stop token.
165   150  
166   @param buffers The const buffer sequence containing data to write. 151   @param buffers The const buffer sequence containing data to write.
167   152  
168   @return An awaitable that await-returns `(error_code,std::size_t)`. 153   @return An awaitable that await-returns `(error_code,std::size_t)`.
169   154  
170   @throws std::system_error When the attached @ref fuse is in 155   @throws std::system_error When the attached @ref fuse is in
171   exception mode and reaches its failure point. 156   exception mode and reaches its failure point.
172   157  
173   @see fuse 158   @see fuse
174   */ 159   */
175   template<ConstBufferSequence CB> 160   template<ConstBufferSequence CB>
176   auto 161   auto
HITCBC 177   531 write_some(CB buffers) 162   531 write_some(CB buffers)
178   { 163   {
179   struct awaitable 164   struct awaitable
180   { 165   {
181   write_stream* self_; 166   write_stream* self_;
182   CB buffers_; 167   CB buffers_;
183   bool canceled_ = false; 168   bool canceled_ = false;
184   169  
HITCBC 185   531 bool await_ready() const noexcept { return false; } 170   531 bool await_ready() const noexcept { return false; }
186   171  
187   // The operation completes synchronously, but await_suspend is 172   // The operation completes synchronously, but await_suspend is
188   // the only place io_env is delivered (the promise's 173   // the only place io_env is delivered (the promise's
189   // transform_awaiter forwards it here). Returning false means 174   // transform_awaiter forwards it here). Returning false means
190   // the coroutine does not actually suspend; it resumes 175   // the coroutine does not actually suspend; it resumes
191   // immediately, having observed the stop token. See io_env, 176   // immediately, having observed the stop token. See io_env,
192   // IoAwaitable. 177   // IoAwaitable.
193   bool 178   bool
HITCBC 194   531 await_suspend( 179   531 await_suspend(
195   std::coroutine_handle<>, 180   std::coroutine_handle<>,
196   io_env const* env) noexcept 181   io_env const* env) noexcept
197   { 182   {
HITCBC 198   531 canceled_ = env->stop_token.stop_requested(); 183   531 canceled_ = env->stop_token.stop_requested();
HITCBC 199   531 return false; 184   531 return false;
200   } 185   }
201   186  
202   [[nodiscard]] io_result<std::size_t> 187   [[nodiscard]] io_result<std::size_t>
HITCBC 203   531 await_resume() 188   531 await_resume()
204   { 189   {
HITCBC 205   531 if(buffer_empty(buffers_)) 190   531 if(buffer_empty(buffers_))
HITCBC 206   3 return {std::error_code(), 0}; 191   3 return {std::error_code(), 0};
207   192  
HITCBC 208   528 if(canceled_) 193   528 if(canceled_)
HITCBC 209   1 return {error::canceled, 0}; 194   1 return {error::canceled, 0};
210   195  
HITCBC 211   527 auto ec = self_->f_.maybe_fail(); 196   527 auto ec = self_->f_.maybe_fail();
HITCBC 212   430 if(ec) 197   430 if(ec)
HITCBC 213   97 return {ec, 0}; 198   97 return {ec, 0};
214   199  
HITCBC 215   333 std::size_t n = buffer_size(buffers_); 200   333 std::size_t n = buffer_size(buffers_);
HITCBC 216   333 n = (std::min)(n, self_->max_write_size_); 201   333 n = (std::min)(n, self_->max_write_size_);
217   202  
HITCBC 218   333 std::size_t const old_size = self_->data_.size(); 203   333 std::size_t const old_size = self_->data_.size();
HITCBC 219   333 self_->data_.resize(old_size + n); 204   333 self_->data_.resize(old_size + n);
HITCBC 220   333 buffer_copy(make_buffer( 205   333 buffer_copy(make_buffer(
HITCBC 221   333 self_->data_.data() + old_size, n), buffers_, n); 206   333 self_->data_.data() + old_size, n), buffers_, n);
222   207  
HITCBC 223   333 ec = self_->consume_match_(); 208   333 ec = self_->consume_match_();
HITCBC 224   333 if(ec) 209   333 if(ec)
225   { 210   {
HITCBC 226   2 self_->data_.resize(old_size); 211   2 self_->data_.resize(old_size);
HITCBC 227   2 return {ec, 0}; 212   2 return {ec, 0};
228   } 213   }
229   214  
HITCBC 230   331 return {std::error_code(), n}; 215   331 return {std::error_code(), n};
231   } 216   }
232   }; 217   };
HITCBC 233   531 return awaitable{this, buffers}; 218   531 return awaitable{this, buffers};
234   } 219   }
235   }; 220   };
236   221  
237   } // test 222   } // test
238   } // capy 223   } // capy
239   } // boost 224   } // boost
240   225  
241   #endif 226   #endif