95.35% Lines (41/43) 88.89% Functions (8/9)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2 - // Copyright (c) 2026 Michael Vandeberg  
3   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
4   // 3   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 6   //
8   // Official repository: https://github.com/cppalliance/capy 7   // Official repository: https://github.com/cppalliance/capy
9   // 8   //
10   9  
11   #ifndef BOOST_CAPY_TEST_WRITE_STREAM_HPP 10   #ifndef BOOST_CAPY_TEST_WRITE_STREAM_HPP
12   #define BOOST_CAPY_TEST_WRITE_STREAM_HPP 11   #define BOOST_CAPY_TEST_WRITE_STREAM_HPP
13   12  
14   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/buffers.hpp> 14   #include <boost/capy/buffers.hpp>
16   #include <boost/capy/buffers/buffer_copy.hpp> 15   #include <boost/capy/buffers/buffer_copy.hpp>
17   #include <boost/capy/buffers/make_buffer.hpp> 16   #include <boost/capy/buffers/make_buffer.hpp>
18   #include <coroutine> 17   #include <coroutine>
19   #include <boost/capy/ex/io_env.hpp> 18   #include <boost/capy/ex/io_env.hpp>
20   #include <boost/capy/io_result.hpp> 19   #include <boost/capy/io_result.hpp>
21   #include <boost/capy/error.hpp> 20   #include <boost/capy/error.hpp>
22   #include <boost/capy/test/fuse.hpp> 21   #include <boost/capy/test/fuse.hpp>
23   22  
24   #include <algorithm> 23   #include <algorithm>
25   #include <string> 24   #include <string>
26   #include <string_view> 25   #include <string_view>
27   26  
28   namespace boost { 27   namespace boost {
29   namespace capy { 28   namespace capy {
30   namespace test { 29   namespace test {
31   30  
32   /** A mock stream for testing write operations. 31   /** A mock stream for testing write operations.
33   32  
34   Use this to verify code that performs writes without needing 33   Use this to verify code that performs writes without needing
35   real I/O. Call @ref write_some to write data, then @ref data 34   real I/O. Call @ref write_some to write data, then @ref data
36   to retrieve what was written. The associated @ref fuse enables 35   to retrieve what was written. The associated @ref fuse enables
37   error injection at controlled points. An optional 36   error injection at controlled points. An optional
38   `max_write_size` constructor parameter limits bytes per write 37   `max_write_size` constructor parameter limits bytes per write
39   to simulate chunked delivery. 38   to simulate chunked delivery.
40   39  
41   This class satisfies the @ref WriteStream concept. 40   This class satisfies the @ref WriteStream concept.
42   41  
43   @par Thread Safety 42   @par Thread Safety
44   Not thread-safe. 43   Not thread-safe.
45   44  
46   @par Example 45   @par Example
47   @code 46   @code
48   fuse f; 47   fuse f;
49   write_stream ws( f ); 48   write_stream ws( f );
50   49  
51   auto r = f.armed( [&]( fuse& ) -> task<void> { 50   auto r = f.armed( [&]( fuse& ) -> task<void> {
52   auto [ec, n] = co_await ws.write_some( 51   auto [ec, n] = co_await ws.write_some(
53   const_buffer( "Hello", 5 ) ); 52   const_buffer( "Hello", 5 ) );
54   if( ec ) 53   if( ec )
55   co_return; 54   co_return;
56   // ws.data() returns "Hello" 55   // ws.data() returns "Hello"
57   } ); 56   } );
58   @endcode 57   @endcode
59   58  
60   @see fuse, WriteStream 59   @see fuse, WriteStream
61   */ 60   */
62   class write_stream 61   class write_stream
63   { 62   {
64   fuse f_; 63   fuse f_;
65   std::string data_; 64   std::string data_;
66   std::string expect_; 65   std::string expect_;
67   std::size_t max_write_size_; 66   std::size_t max_write_size_;
68   67  
69   std::error_code 68   std::error_code
HITCBC 70   959 consume_match_() noexcept 69   959 consume_match_() noexcept
71   { 70   {
HITCBC 72   959 if(data_.empty() || expect_.empty()) 71   959 if(data_.empty() || expect_.empty())
HITCBC 73   943 return {}; 72   943 return {};
HITCBC 74   16 std::size_t const n = (std::min)(data_.size(), expect_.size()); 73   16 std::size_t const n = (std::min)(data_.size(), expect_.size());
HITCBC 75   16 if(std::string_view(data_.data(), n) != 74   16 if(std::string_view(data_.data(), n) !=
HITCBC 76   32 std::string_view(expect_.data(), n)) 75   32 std::string_view(expect_.data(), n))
HITCBC 77   4 return error::test_failure; 76   4 return error::test_failure;
HITCBC 78   12 data_.erase(0, n); 77   12 data_.erase(0, n);
HITCBC 79   12 expect_.erase(0, n); 78   12 expect_.erase(0, n);
HITCBC 80   12 return {}; 79   12 return {};
81   } 80   }
82   81  
83   public: 82   public:
84   /** Construct a write stream. 83   /** Construct a write stream.
85   84  
86   @param f The fuse used to inject errors during writes. 85   @param f The fuse used to inject errors during writes.
87   86  
88   @param max_write_size Maximum bytes transferred per write. 87   @param max_write_size Maximum bytes transferred per write.
89   Use to simulate chunked network delivery. 88   Use to simulate chunked network delivery.
90   */ 89   */
HITCBC 91   1192 explicit write_stream( 90   1191 explicit write_stream(
92   fuse f = {}, 91   fuse f = {},
93   std::size_t max_write_size = std::size_t(-1)) noexcept 92   std::size_t max_write_size = std::size_t(-1)) noexcept
HITCBC 94   1192 : f_(std::move(f)) 93   1191 : f_(std::move(f))
HITCBC 95   1192 , max_write_size_(max_write_size) 94   1191 , max_write_size_(max_write_size)
96   { 95   {
HITCBC 97   1192 } 96   1191 }
98   97  
99   /// Return the written data as a string view. 98   /// Return the written data as a string view.
100   std::string_view 99   std::string_view
HITCBC 101   962 data() const noexcept 100   962 data() const noexcept
102   { 101   {
HITCBC 103   962 return data_; 102   962 return data_;
104   } 103   }
105   104  
106   /** Set the expected data for subsequent writes. 105   /** Set the expected data for subsequent writes.
107   106  
108   Stores the expected data and immediately tries to match 107   Stores the expected data and immediately tries to match
109   against any data already written. Matched data is consumed 108   against any data already written. Matched data is consumed
110   from both buffers. 109   from both buffers.
111   110  
112   @param sv The expected data. 111   @param sv The expected data.
113   112  
114   @return An error if existing data does not match. 113   @return An error if existing data does not match.
115   */ 114   */
116   std::error_code 115   std::error_code
HITCBC 117   30 expect(std::string_view sv) 116   30 expect(std::string_view sv)
118   { 117   {
HITCBC 119   30 expect_.assign(sv); 118   30 expect_.assign(sv);
HITCBC 120   30 return consume_match_(); 119   30 return consume_match_();
121   } 120   }
122   121  
123   /// Return the number of bytes written. 122   /// Return the number of bytes written.
124   std::size_t 123   std::size_t
HITCBC 125   7 size() const noexcept 124   6 size() const noexcept
126   { 125   {
HITCBC 127   7 return data_.size(); 126   6 return data_.size();
128   } 127   }
129   128  
130   /** Asynchronously write data to the stream. 129   /** Asynchronously write data to the stream.
131   130  
132   Transfers up to `buffer_size( buffers )` bytes from the provided 131   Transfers up to `buffer_size( buffers )` bytes from the provided
133   const buffer sequence to the internal buffer. Before every write, 132   const buffer sequence to the internal buffer. Before every write,
134   the attached @ref fuse is consulted to possibly inject an error 133   the attached @ref fuse is consulted to possibly inject an error
135   for testing fault scenarios. The returned `std::size_t` is the 134   for testing fault scenarios. The returned `std::size_t` is the
136   number of bytes transferred. 135   number of bytes transferred.
137   136  
138   @par Effects 137   @par Effects
139   On success, appends the written bytes to the internal buffer. 138   On success, appends the written bytes to the internal buffer.
140   If an error is injected by the fuse, the internal buffer remains 139   If an error is injected by the fuse, the internal buffer remains
141   unchanged. 140   unchanged.
142   141  
143   @par Exception Safety 142   @par Exception Safety
144 - No-throw guarantee. 143 + Injected I/O conditions are reported via the `error_code`
145 - 144 + component of the result. Throws `std::system_error` only when
146 - @par Cancellation 145 + the attached @ref fuse is in exception mode and reaches its
147 - If the environment's stop token has been requested, the write 146 + failure point; no-throw otherwise.
148 - completes immediately with `error::canceled` and transfers no  
149 - data. An empty buffer sequence is a no-op that completes  
150 - successfully regardless of the stop token.  
151   147  
152   @param buffers The const buffer sequence containing data to write. 148   @param buffers The const buffer sequence containing data to write.
153   149  
154   @return An awaitable that await-returns `(error_code,std::size_t)`. 150   @return An awaitable that await-returns `(error_code,std::size_t)`.
155   151  
  152 + @throws std::system_error When the attached @ref fuse is in
  153 + exception mode and reaches its failure point.
  154 +
156   @see fuse 155   @see fuse
157   */ 156   */
158   template<ConstBufferSequence CB> 157   template<ConstBufferSequence CB>
159   auto 158   auto
HITCBC 160   1194 write_some(CB buffers) 159   1193 write_some(CB buffers)
161   { 160   {
162   struct awaitable 161   struct awaitable
163   { 162   {
164   write_stream* self_; 163   write_stream* self_;
165 - bool canceled_ = false;  
166   CB buffers_; 164   CB buffers_;
167   165  
HITCBC 168 - 1194 bool await_ready() const noexcept { return false; } 166 + 1193 bool await_ready() const noexcept { return true; }
169   167  
170 - // The operation completes synchronously, but await_suspend is 168 + // This method is required to satisfy Capy's IoAwaitable concept,
171 - // the only place io_env is delivered (the promise's 169 + // but is never called because await_ready() returns true.
172 - // transform_awaiter forwards it here). Returning false means 170 + //
173 - // the coroutine does not actually suspend; it resumes 171 + // Capy uses a two-layer awaitable system: the promise's
174 - // immediately, having observed the stop token. See io_env, 172 + // await_transform wraps awaitables in a transform_awaiter whose
175 - // IoAwaitable. 173 + // standard await_suspend(coroutine_handle) calls this custom
176 - bool 174 + // 2-argument overload, passing the io_env from the coroutine's
ECB 177 - 1194 await_suspend( 175 + // context. For synchronous test awaitables like this one, the
  176 + // coroutine never suspends, so this is not invoked. The signature
  177 + // exists to allow the same awaitable type to work with both
  178 + // synchronous (test) and asynchronous (real I/O) code.
MISUNC   179 + void await_suspend(
178   std::coroutine_handle<>, 180   std::coroutine_handle<>,
179 - io_env const* env) noexcept 181 + io_env const*) const noexcept
180 - canceled_ = env->stop_token.stop_requested();  
DCB 181 - 1194 return false;  
ECB 182   1194 { 182   {
MISUIC 183   } 183   }
184   184  
185   io_result<std::size_t> 185   io_result<std::size_t>
HITCBC 186   1194 await_resume() 186   1193 await_resume()
187   { 187   {
HITCBC 188   1194 if(buffer_empty(buffers_)) 188   1193 if(buffer_empty(buffers_))
DCB 189 - 2  
190 - if(canceled_)  
DCB 191 - 1192 return {error::canceled, 0};  
HITCBC 192   1 return {{}, 0}; 189   2 return {{}, 0};
193   190  
HITCBC 194   1191 auto ec = self_->f_.maybe_fail(); 191   1191 auto ec = self_->f_.maybe_fail();
HITCBC 195   1060 if(ec) 192   1060 if(ec)
HITCBC 196   131 return {ec, 0}; 193   131 return {ec, 0};
197   194  
HITCBC 198   929 std::size_t n = buffer_size(buffers_); 195   929 std::size_t n = buffer_size(buffers_);
HITCBC 199   929 n = (std::min)(n, self_->max_write_size_); 196   929 n = (std::min)(n, self_->max_write_size_);
200   197  
HITCBC 201   929 std::size_t const old_size = self_->data_.size(); 198   929 std::size_t const old_size = self_->data_.size();
HITCBC 202   929 self_->data_.resize(old_size + n); 199   929 self_->data_.resize(old_size + n);
HITCBC 203   929 buffer_copy(make_buffer( 200   929 buffer_copy(make_buffer(
HITCBC 204   929 self_->data_.data() + old_size, n), buffers_, n); 201   929 self_->data_.data() + old_size, n), buffers_, n);
205   202  
HITCBC 206   929 ec = self_->consume_match_(); 203   929 ec = self_->consume_match_();
HITCBC 207   929 if(ec) 204   929 if(ec)
208   { 205   {
HITCBC 209   2 self_->data_.resize(old_size); 206   2 self_->data_.resize(old_size);
HITCBC 210   2 return {ec, 0}; 207   2 return {ec, 0};
211   } 208   }
212   209  
HITCBC 213   927 return {{}, n}; 210   927 return {{}, n};
214   } 211   }
215   }; 212   };
HITCBC 216   1194 return awaitable{this, buffers}; 213   1193 return awaitable{this, buffers};
217   } 214   }
218   }; 215   };
219   216  
220   } // test 217   } // test
221   } // capy 218   } // capy
222   } // boost 219   } // boost
223   220  
224   #endif 221   #endif