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