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