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