90.91% Lines (50/55) 100.00% Functions (11/11)
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/http 8   // Official repository: https://github.com/cppalliance/http
9   // 9   //
10   10  
11   #ifndef BOOST_HTTP_TEST_READ_SOURCE_HPP 11   #ifndef BOOST_HTTP_TEST_READ_SOURCE_HPP
12   #define BOOST_HTTP_TEST_READ_SOURCE_HPP 12   #define BOOST_HTTP_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 http { 28   namespace http {
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 capy::test::fuse enables error injection 35   to consume it. The associated @ref capy::test::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   capy::test::fuse f; 48   capy::test::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( [&]( capy::test::fuse& ) -> task<void> { 53   auto r = f.armed( [&]( capy::test::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   capy::mutable_buffer( buf, sizeof( buf ) ) ); 56   capy::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 capy::test::fuse, ReadSource 63   @see capy::test::fuse, ReadSource
64   */ 64   */
65   class read_source 65   class read_source
66   { 66   {
67   capy::test::fuse f_; 67   capy::test::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 capy::test::fuse used to inject errors during reads. 75   @param f The capy::test::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   277 explicit read_source( 80   277 explicit read_source(
81   capy::test::fuse f = {}, 81   capy::test::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   277 : f_(std::move(f)) 83   277 : f_(std::move(f))
HITCBC 84   277 , max_read_size_(max_read_size) 84   277 , max_read_size_(max_read_size)
85   { 85   {
HITCBC 86   277 } 86   277 }
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   258 provide(std::string_view sv) 95   258 provide(std::string_view sv)
96   { 96   {
HITCBC 97   258 data_.append(sv); 97   258 data_.append(sv);
HITCBC 98   258 } 98   258 }
99   99  
100   /// Clear all data and reset the read position. 100   /// Clear all data and reset the read position.
101   void 101   void
102   clear() noexcept 102   clear() noexcept
103   { 103   {
104   data_.clear(); 104   data_.clear();
105   pos_ = 0; 105   pos_ = 0;
106   } 106   }
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   8 available() const noexcept 110   8 available() const noexcept
111   { 111   {
HITCBC 112   8 return data_.size() - pos_; 112   8 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 `capy::buffer_size( buffers )` bytes from the internal 117   Transfers up to `capy::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 `capy::error::eof`. Before every read, the attached 119   remains, returns `capy::error::eof`. Before every read, the attached
120   @ref capy::test::fuse is consulted to possibly inject an error for testing 120   @ref capy::test::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 `capy::error::canceled` and transfers no 129   completes immediately with `capy::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 capy::test::fuse 133   @see capy::test::fuse
134   */ 134   */
135   template<capy::MutableBufferSequence MB> 135   template<capy::MutableBufferSequence MB>
136   auto 136   auto
HITCBC 137   50 read_some(MB buffers) 137   50 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   50 bool await_ready() const noexcept { return false; } 145   50 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 capy::io_env is delivered (the promise's 148   // the only place capy::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 capy::io_env, 151   // immediately, having observed the stop token. See capy::io_env,
152   // IoAwaitable. 152   // IoAwaitable.
153   bool 153   bool
HITCBC 154   50 await_suspend( 154   50 await_suspend(
155   std::coroutine_handle<>, 155   std::coroutine_handle<>,
156   capy::io_env const* env) noexcept 156   capy::io_env const* env) noexcept
157   { 157   {
HITCBC 158   50 canceled_ = env->stop_token.stop_requested(); 158   50 canceled_ = env->stop_token.stop_requested();
HITCBC 159   50 return false; 159   50 return false;
160   } 160   }
161   161  
162   capy::io_result<std::size_t> 162   capy::io_result<std::size_t>
HITCBC 163   50 await_resume() 163   50 await_resume()
164   { 164   {
HITCBC 165   50 if(capy::buffer_empty(buffers_)) 165   50 if(capy::buffer_empty(buffers_))
MISUBC 166   return {std::error_code(), 0}; 166   return {std::error_code(), 0};
167   167  
HITCBC 168   50 if(canceled_) 168   50 if(canceled_)
MISUBC 169   return {capy::error::canceled, 0}; 169   return {capy::error::canceled, 0};
170   170  
HITCBC 171   50 auto ec = self_->f_.maybe_fail(); 171   50 auto ec = self_->f_.maybe_fail();
HITCBC 172   36 if(ec) 172   36 if(ec)
HITCBC 173   14 return {ec, 0}; 173   14 return {ec, 0};
174   174  
HITCBC 175   22 if(self_->pos_ >= self_->data_.size()) 175   22 if(self_->pos_ >= self_->data_.size())
HITCBC 176   2 return {capy::error::eof, 0}; 176   2 return {capy::error::eof, 0};
177   177  
HITCBC 178   20 std::size_t avail = self_->data_.size() - self_->pos_; 178   20 std::size_t avail = self_->data_.size() - self_->pos_;
HITCBC 179   20 if(avail > self_->max_read_size_) 179   20 if(avail > self_->max_read_size_)
MISUBC 180   avail = self_->max_read_size_; 180   avail = self_->max_read_size_;
HITCBC 181   20 auto src = capy::make_buffer(self_->data_.data() + self_->pos_, avail); 181   20 auto src = capy::make_buffer(self_->data_.data() + self_->pos_, avail);
HITCBC 182   20 std::size_t const n = capy::buffer_copy(buffers_, src); 182   20 std::size_t const n = capy::buffer_copy(buffers_, src);
HITCBC 183   20 self_->pos_ += n; 183   20 self_->pos_ += n;
HITCBC 184   20 return {std::error_code(), n}; 184   20 return {std::error_code(), n};
185   } 185   }
186   }; 186   };
HITCBC 187   50 return awaitable{this, buffers}; 187   50 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   `capy::error::eof` with the number of bytes transferred. Before 194   `capy::error::eof` with the number of bytes transferred. Before
195   every read, the attached @ref capy::test::fuse is consulted to possibly 195   every read, the attached @ref capy::test::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 `capy::error::canceled` and transfers no 208   completes immediately with `capy::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 capy::test::fuse 212   @see capy::test::fuse
213   */ 213   */
214   template<capy::MutableBufferSequence MB> 214   template<capy::MutableBufferSequence MB>
215   auto 215   auto
HITCBC 216   292 read(MB buffers) 216   292 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   292 bool await_ready() const noexcept { return false; } 224   292 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   292 await_suspend( 229   292 await_suspend(
230   std::coroutine_handle<>, 230   std::coroutine_handle<>,
231   capy::io_env const* env) noexcept 231   capy::io_env const* env) noexcept
232   { 232   {
HITCBC 233   292 canceled_ = env->stop_token.stop_requested(); 233   292 canceled_ = env->stop_token.stop_requested();
HITCBC 234   292 return false; 234   292 return false;
235   } 235   }
236   236  
237   capy::io_result<std::size_t> 237   capy::io_result<std::size_t>
HITCBC 238   292 await_resume() 238   292 await_resume()
239   { 239   {
HITCBC 240   292 if(capy::buffer_empty(buffers_)) 240   292 if(capy::buffer_empty(buffers_))
MISUBC 241   return {std::error_code(), 0}; 241   return {std::error_code(), 0};
242   242  
HITCBC 243   292 if(canceled_) 243   292 if(canceled_)
MISUBC 244   return {capy::error::canceled, 0}; 244   return {capy::error::canceled, 0};
245   245  
HITCBC 246   292 auto ec = self_->f_.maybe_fail(); 246   292 auto ec = self_->f_.maybe_fail();
HITCBC 247   232 if(ec) 247   232 if(ec)
HITCBC 248   60 return {ec, 0}; 248   60 return {ec, 0};
249   249  
HITCBC 250   172 if(self_->pos_ >= self_->data_.size()) 250   172 if(self_->pos_ >= self_->data_.size())
HITCBC 251   16 return {capy::error::eof, 0}; 251   16 return {capy::error::eof, 0};
252   252  
HITCBC 253   156 std::size_t avail = self_->data_.size() - self_->pos_; 253   156 std::size_t avail = self_->data_.size() - self_->pos_;
HITCBC 254   156 auto src = capy::make_buffer(self_->data_.data() + self_->pos_, avail); 254   156 auto src = capy::make_buffer(self_->data_.data() + self_->pos_, avail);
HITCBC 255   156 std::size_t const n = capy::buffer_copy(buffers_, src); 255   156 std::size_t const n = capy::buffer_copy(buffers_, src);
HITCBC 256   156 self_->pos_ += n; 256   156 self_->pos_ += n;
257   257  
HITCBC 258   156 if(n < capy::buffer_size(buffers_)) 258   156 if(n < capy::buffer_size(buffers_))
HITCBC 259   64 return {capy::error::eof, n}; 259   64 return {capy::error::eof, n};
HITCBC 260   92 return {std::error_code(), n}; 260   92 return {std::error_code(), n};
261   } 261   }
262   }; 262   };
HITCBC 263   292 return awaitable{this, buffers}; 263   292 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