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