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