100.00% Lines (59/59) 100.00% Functions (12/12)
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_READ_SOURCE_HPP 11   #ifndef BOOST_CAPY_TEST_READ_SOURCE_HPP
12   #define BOOST_CAPY_TEST_READ_SOURCE_HPP 12   #define BOOST_CAPY_TEST_READ_SOURCE_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 <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 source for testing read operations. 31   /** A mock source for testing read operations.
32   32  
33   Use this to verify code that performs complete reads without needing 33   Use this to verify code that performs complete reads without needing
34   real I/O. Call @ref provide to supply data, then @ref read 34   real I/O. Call @ref provide to supply data, then @ref read
35   to consume it. The associated @ref fuse enables error injection 35   to consume it. The associated @ref fuse enables error injection
36   at controlled points. 36   at controlled points.
37   37  
38   This class satisfies the @ref ReadSource concept by providing both 38   This class satisfies the @ref ReadSource concept by providing both
39   partial reads via `read_some` (satisfying @ref ReadStream) and 39   partial reads via `read_some` (satisfying @ref ReadStream) and
40   complete reads via `read` that fill the entire buffer sequence 40   complete reads via `read` that fill the entire buffer sequence
41   before returning. 41   before returning.
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   read_source rs( f ); 49   read_source rs( f );
50   rs.provide( "Hello, " ); 50   rs.provide( "Hello, " );
51   rs.provide( "World!" ); 51   rs.provide( "World!" );
52   52  
53   auto r = f.armed( [&]( fuse& ) -> task<void> { 53   auto r = f.armed( [&]( fuse& ) -> task<void> {
54   char buf[32]; 54   char buf[32];
55   auto [ec, n] = co_await rs.read( 55   auto [ec, n] = co_await rs.read(
56   mutable_buffer( buf, sizeof( buf ) ) ); 56   mutable_buffer( buf, sizeof( buf ) ) );
57   if( ec ) 57   if( ec )
58   co_return; 58   co_return;
59   // buf contains "Hello, World!" 59   // buf contains "Hello, World!"
60   } ); 60   } );
61   @endcode 61   @endcode
62   62  
63   @see fuse, ReadSource 63   @see fuse, ReadSource
64   */ 64   */
65   class read_source 65   class read_source
66   { 66   {
67   fuse f_; 67   fuse f_;
68   std::string data_; 68   std::string data_;
69   std::size_t pos_ = 0; 69   std::size_t pos_ = 0;
70   std::size_t max_read_size_; 70   std::size_t max_read_size_;
71   71  
72   public: 72   public:
73   /** Construct a read source. 73   /** Construct a read source.
74   74  
75   @param f The fuse used to inject errors during reads. 75   @param f The fuse used to inject errors during reads.
76   76  
77   @param max_read_size Maximum bytes returned per read. 77   @param max_read_size Maximum bytes returned per read.
78   Use to simulate chunked delivery. 78   Use to simulate chunked delivery.
79   */ 79   */
HITCBC 80   397 explicit read_source( 80   397 explicit read_source(
81   fuse f = {}, 81   fuse f = {},
82   std::size_t max_read_size = std::size_t(-1)) noexcept 82   std::size_t max_read_size = std::size_t(-1)) noexcept
HITCBC 83   397 : f_(std::move(f)) 83   397 : f_(std::move(f))
HITCBC 84   397 , max_read_size_(max_read_size) 84   397 , max_read_size_(max_read_size)
85   { 85   {
HITCBC 86   397 } 86   397 }
87   87  
88   /** Append data to be returned by subsequent reads. 88   /** Append data to be returned by subsequent reads.
89   89  
90   Multiple calls accumulate data that @ref read returns. 90   Multiple calls accumulate data that @ref read returns.
91   91  
92   @param sv The data to append. 92   @param sv The data to append.
93   */ 93   */
94   void 94   void
HITCBC 95   364 provide(std::string_view sv) 95   364 provide(std::string_view sv)
96   { 96   {
HITCBC 97   364 data_.append(sv); 97   364 data_.append(sv);
HITCBC 98   364 } 98   364 }
99   99  
100   /// Clear all data and reset the read position. 100   /// Clear all data and reset the read position.
101   void 101   void
HITCBC 102   2 clear() noexcept 102   2 clear() noexcept
103   { 103   {
HITCBC 104   2 data_.clear(); 104   2 data_.clear();
HITCBC 105   2 pos_ = 0; 105   2 pos_ = 0;
HITCBC 106   2 } 106   2 }
107   107  
108   /// Return the number of bytes available for reading. 108   /// Return the number of bytes available for reading.
109   std::size_t 109   std::size_t
HITCBC 110   30 available() const noexcept 110   30 available() const noexcept
111   { 111   {
HITCBC 112   30 return data_.size() - pos_; 112   30 return data_.size() - pos_;
113   } 113   }
114   114  
115   /** Asynchronously read some data from the source. 115   /** Asynchronously read some data from the source.
116   116  
117   Transfers up to `buffer_size( buffers )` bytes from the internal 117   Transfers up to `buffer_size( buffers )` bytes from the internal
118   buffer to the provided mutable buffer sequence. If no data 118   buffer to the provided mutable buffer sequence. If no data
119   remains, returns `error::eof`. Before every read, the attached 119   remains, returns `error::eof`. Before every read, the attached
120   @ref fuse is consulted to possibly inject an error for testing 120   @ref fuse is consulted to possibly inject an error for testing
121   fault scenarios. 121   fault scenarios.
122   122  
123   @param buffers The mutable buffer sequence to receive data. 123   @param buffers The mutable buffer sequence to receive data.
124   124  
125   @return An awaitable that await-returns `(error_code,std::size_t)`. 125   @return An awaitable that await-returns `(error_code,std::size_t)`.
126   126  
127   @par Cancellation 127   @par Cancellation
128   If the environment's stop token has been requested, the read 128   If the environment's stop token has been requested, the read
129   completes immediately with `error::canceled` and transfers no 129   completes immediately with `error::canceled` and transfers no
130   data. An empty buffer sequence is a no-op that completes 130   data. An empty buffer sequence is a no-op that completes
131   successfully regardless of the stop token. 131   successfully regardless of the stop token.
132   132  
133   @see fuse 133   @see fuse
134   */ 134   */
135   template<MutableBufferSequence MB> 135   template<MutableBufferSequence MB>
136   auto 136   auto
HITCBC 137   117 read_some(MB buffers) 137   117 read_some(MB buffers)
138   { 138   {
139   struct awaitable 139   struct awaitable
140   { 140   {
141   read_source* self_; 141   read_source* self_;
142   MB buffers_; 142   MB buffers_;
143   bool canceled_ = false; 143   bool canceled_ = false;
144   144  
HITCBC 145   117 bool await_ready() const noexcept { return false; } 145   117 bool await_ready() const noexcept { return false; }
146   146  
147   // The operation completes synchronously, but await_suspend is 147   // The operation completes synchronously, but await_suspend is
148   // the only place io_env is delivered (the promise's 148   // the only place io_env is delivered (the promise's
149   // transform_awaiter forwards it here). Returning false means 149   // transform_awaiter forwards it here). Returning false means
150   // the coroutine does not actually suspend; it resumes 150   // the coroutine does not actually suspend; it resumes
151   // immediately, having observed the stop token. See io_env, 151   // immediately, having observed the stop token. See io_env,
152   // IoAwaitable. 152   // IoAwaitable.
153   bool 153   bool
HITCBC 154   117 await_suspend( 154   117 await_suspend(
155   std::coroutine_handle<>, 155   std::coroutine_handle<>,
156   io_env const* env) noexcept 156   io_env const* env) noexcept
157   { 157   {
HITCBC 158   117 canceled_ = env->stop_token.stop_requested(); 158   117 canceled_ = env->stop_token.stop_requested();
HITCBC 159   117 return false; 159   117 return false;
160   } 160   }
161   161  
162   io_result<std::size_t> 162   io_result<std::size_t>
HITCBC 163   117 await_resume() 163   117 await_resume()
164   { 164   {
HITCBC 165   117 if(buffer_empty(buffers_)) 165   117 if(buffer_empty(buffers_))
HITCBC 166   4 return {{}, 0}; 166   4 return {{}, 0};
167   167  
HITCBC 168   113 if(canceled_) 168   113 if(canceled_)
HITCBC 169   1 return {error::canceled, 0}; 169   1 return {error::canceled, 0};
170   170  
HITCBC 171   112 auto ec = self_->f_.maybe_fail(); 171   112 auto ec = self_->f_.maybe_fail();
HITCBC 172   80 if(ec) 172   80 if(ec)
HITCBC 173   32 return {ec, 0}; 173   32 return {ec, 0};
174   174  
HITCBC 175   48 if(self_->pos_ >= self_->data_.size()) 175   48 if(self_->pos_ >= self_->data_.size())
HITCBC 176   4 return {error::eof, 0}; 176   4 return {error::eof, 0};
177   177  
HITCBC 178   44 std::size_t avail = self_->data_.size() - self_->pos_; 178   44 std::size_t avail = self_->data_.size() - self_->pos_;
HITCBC 179   44 if(avail > self_->max_read_size_) 179   44 if(avail > self_->max_read_size_)
HITCBC 180   14 avail = self_->max_read_size_; 180   14 avail = self_->max_read_size_;
HITCBC 181   44 auto src = make_buffer(self_->data_.data() + self_->pos_, avail); 181   44 auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
HITCBC 182   44 std::size_t const n = buffer_copy(buffers_, src); 182   44 std::size_t const n = buffer_copy(buffers_, src);
HITCBC 183   44 self_->pos_ += n; 183   44 self_->pos_ += n;
HITCBC 184   44 return {{}, n}; 184   44 return {{}, n};
185   } 185   }
186   }; 186   };
HITCBC 187   117 return awaitable{this, buffers}; 187   117 return awaitable{this, buffers};
188   } 188   }
189   189  
190   /** Asynchronously read data from the source. 190   /** Asynchronously read data from the source.
191   191  
192   Fills the entire buffer sequence from the internal data. 192   Fills the entire buffer sequence from the internal data.
193   If the available data is less than the buffer size, returns 193   If the available data is less than the buffer size, returns
194   `error::eof` with the number of bytes transferred. Before 194   `error::eof` with the number of bytes transferred. Before
195   every read, the attached @ref fuse is consulted to possibly 195   every read, the attached @ref fuse is consulted to possibly
196   inject an error for testing fault scenarios. 196   inject an error for testing fault scenarios.
197   197  
198   Unlike @ref read_some, this ignores `max_read_size` and 198   Unlike @ref read_some, this ignores `max_read_size` and
199   transfers all available data in a single operation, matching 199   transfers all available data in a single operation, matching
200   the @ref ReadSource semantic contract. 200   the @ref ReadSource semantic contract.
201   201  
202   @param buffers The mutable buffer sequence to receive data. 202   @param buffers The mutable buffer sequence to receive data.
203   203  
204   @return An awaitable that await-returns `(error_code,std::size_t)`. 204   @return An awaitable that await-returns `(error_code,std::size_t)`.
205   205  
206   @par Cancellation 206   @par Cancellation
207   If the environment's stop token has been requested, the read 207   If the environment's stop token has been requested, the read
208   completes immediately with `error::canceled` and transfers no 208   completes immediately with `error::canceled` and transfers no
209   data. An empty buffer sequence is a no-op that completes 209   data. An empty buffer sequence is a no-op that completes
210   successfully regardless of the stop token. 210   successfully regardless of the stop token.
211   211  
212   @see fuse 212   @see fuse
213   */ 213   */
214   template<MutableBufferSequence MB> 214   template<MutableBufferSequence MB>
215   auto 215   auto
HITCBC 216   367 read(MB buffers) 216   367 read(MB buffers)
217   { 217   {
218   struct awaitable 218   struct awaitable
219   { 219   {
220   read_source* self_; 220   read_source* self_;
221   MB buffers_; 221   MB buffers_;
222   bool canceled_ = false; 222   bool canceled_ = false;
223   223  
HITCBC 224   367 bool await_ready() const noexcept { return false; } 224   367 bool await_ready() const noexcept { return false; }
225   225  
226   // Reads the stop token without suspending; see the comment 226   // Reads the stop token without suspending; see the comment
227   // on read_some() for details. 227   // on read_some() for details.
228   bool 228   bool
HITCBC 229   367 await_suspend( 229   367 await_suspend(
230   std::coroutine_handle<>, 230   std::coroutine_handle<>,
231   io_env const* env) noexcept 231   io_env const* env) noexcept
232   { 232   {
HITCBC 233   367 canceled_ = env->stop_token.stop_requested(); 233   367 canceled_ = env->stop_token.stop_requested();
HITCBC 234   367 return false; 234   367 return false;
235   } 235   }
236   236  
237   io_result<std::size_t> 237   io_result<std::size_t>
HITCBC 238   367 await_resume() 238   367 await_resume()
239   { 239   {
HITCBC 240   367 if(buffer_empty(buffers_)) 240   367 if(buffer_empty(buffers_))
HITCBC 241   2 return {{}, 0}; 241   2 return {{}, 0};
242   242  
HITCBC 243   365 if(canceled_) 243   365 if(canceled_)
HITCBC 244   1 return {error::canceled, 0}; 244   1 return {error::canceled, 0};
245   245  
HITCBC 246   364 auto ec = self_->f_.maybe_fail(); 246   364 auto ec = self_->f_.maybe_fail();
HITCBC 247   285 if(ec) 247   285 if(ec)
HITCBC 248   79 return {ec, 0}; 248   79 return {ec, 0};
249   249  
HITCBC 250   206 if(self_->pos_ >= self_->data_.size()) 250   206 if(self_->pos_ >= self_->data_.size())
HITCBC 251   18 return {error::eof, 0}; 251   18 return {error::eof, 0};
252   252  
HITCBC 253   188 std::size_t avail = self_->data_.size() - self_->pos_; 253   188 std::size_t avail = self_->data_.size() - self_->pos_;
HITCBC 254   188 auto src = make_buffer(self_->data_.data() + self_->pos_, avail); 254   188 auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
HITCBC 255   188 std::size_t const n = buffer_copy(buffers_, src); 255   188 std::size_t const n = buffer_copy(buffers_, src);
HITCBC 256   188 self_->pos_ += n; 256   188 self_->pos_ += n;
257   257  
HITCBC 258   188 if(n < buffer_size(buffers_)) 258   188 if(n < buffer_size(buffers_))
HITCBC 259   72 return {error::eof, n}; 259   72 return {error::eof, n};
HITCBC 260   116 return {{}, n}; 260   116 return {{}, n};
261   } 261   }
262   }; 262   };
HITCBC 263   367 return awaitable{this, buffers}; 263   367 return awaitable{this, buffers};
264   } 264   }
265   }; 265   };
266   266  
267   } // test 267   } // test
268   } // capy 268   } // capy
269   } // boost 269   } // boost
270   270  
271   #endif 271   #endif