93.55% Lines (29/31) 100.00% Functions (7/7)
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_BUFFER_SOURCE_HPP 11   #ifndef BOOST_HTTP_TEST_BUFFER_SOURCE_HPP
12   #define BOOST_HTTP_TEST_BUFFER_SOURCE_HPP 12   #define BOOST_HTTP_TEST_BUFFER_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/make_buffer.hpp> 16   #include <boost/capy/buffers/make_buffer.hpp>
17   #include <coroutine> 17   #include <coroutine>
18   #include <boost/capy/error.hpp> 18   #include <boost/capy/error.hpp>
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/test/fuse.hpp> 21   #include <boost/capy/test/fuse.hpp>
22   22  
23   #include <algorithm> 23   #include <algorithm>
24   #include <span> 24   #include <span>
25   #include <string> 25   #include <string>
26   #include <string_view> 26   #include <string_view>
27   27  
28   namespace boost { 28   namespace boost {
29   namespace http { 29   namespace http {
30   namespace test { 30   namespace test {
31   31  
32   /** A mock buffer source for testing pull (BufferSource) operations. 32   /** A mock buffer source for testing pull (BufferSource) operations.
33   33  
34   Use this to verify code that transfers data from a buffer source to 34   Use this to verify code that transfers data from a buffer source to
35   a sink without needing real I/O. Call @ref provide to supply data, 35   a sink without needing real I/O. Call @ref provide to supply data,
36   then @ref pull to retrieve buffer descriptors. The associated 36   then @ref pull to retrieve buffer descriptors. The associated
37   @ref capy::test::fuse enables error injection at controlled points. 37   @ref capy::test::fuse enables error injection at controlled points.
38   38  
39   This class satisfies the @ref BufferSource concept by providing 39   This class satisfies the @ref BufferSource concept by providing
40   a pull interface that fills an array of buffer descriptors and 40   a pull interface that fills an array of buffer descriptors and
41   a consume interface to indicate bytes used. 41   a consume interface to indicate bytes used.
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   buffer_source bs( f ); 49   buffer_source bs( f );
50   bs.provide( "Hello, " ); 50   bs.provide( "Hello, " );
51   bs.provide( "World!" ); 51   bs.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   capy::const_buffer arr[16]; 54   capy::const_buffer arr[16];
55   auto [ec, bufs] = co_await bs.pull( arr ); 55   auto [ec, bufs] = co_await bs.pull( arr );
56   if( ec ) 56   if( ec )
57   co_return; 57   co_return;
58   // bufs contains buffer descriptors 58   // bufs contains buffer descriptors
59   std::size_t n = capy::buffer_size( bufs ); 59   std::size_t n = capy::buffer_size( bufs );
60   bs.consume( n ); 60   bs.consume( n );
61   } ); 61   } );
62   @endcode 62   @endcode
63   63  
64   @see capy::test::fuse, BufferSource 64   @see capy::test::fuse, BufferSource
65   */ 65   */
66   class buffer_source 66   class buffer_source
67   { 67   {
68   capy::test::fuse f_; 68   capy::test::fuse f_;
69   std::string data_; 69   std::string data_;
70   std::size_t pos_ = 0; 70   std::size_t pos_ = 0;
71   std::size_t max_pull_size_; 71   std::size_t max_pull_size_;
72   72  
73   public: 73   public:
74   /** Construct a buffer source. 74   /** Construct a buffer source.
75   75  
76   @param f The capy::test::fuse used to inject errors during pulls. 76   @param f The capy::test::fuse used to inject errors during pulls.
77   77  
78   @param max_pull_size Maximum bytes returned per pull. 78   @param max_pull_size Maximum bytes returned per pull.
79   Use to simulate chunked delivery. 79   Use to simulate chunked delivery.
80   */ 80   */
HITCBC 81   302 explicit buffer_source( 81   302 explicit buffer_source(
82   capy::test::fuse f = {}, 82   capy::test::fuse f = {},
83   std::size_t max_pull_size = std::size_t(-1)) noexcept 83   std::size_t max_pull_size = std::size_t(-1)) noexcept
HITCBC 84   302 : f_(std::move(f)) 84   302 : f_(std::move(f))
HITCBC 85   302 , max_pull_size_(max_pull_size) 85   302 , max_pull_size_(max_pull_size)
86   { 86   {
HITCBC 87   302 } 87   302 }
88   88  
89   /** Append data to be returned by subsequent pulls. 89   /** Append data to be returned by subsequent pulls.
90   90  
91   Multiple calls accumulate data that @ref pull returns. 91   Multiple calls accumulate data that @ref pull returns.
92   92  
93   @param sv The data to append. 93   @param sv The data to append.
94   */ 94   */
95   void 95   void
HITCBC 96   316 provide(std::string_view sv) 96   316 provide(std::string_view sv)
97   { 97   {
HITCBC 98   316 data_.append(sv); 98   316 data_.append(sv);
HITCBC 99   316 } 99   316 }
100   100  
101   /// Clear all data and reset the read position. 101   /// Clear all data and reset the read position.
102   void 102   void
103   clear() noexcept 103   clear() noexcept
104   { 104   {
105   data_.clear(); 105   data_.clear();
106   pos_ = 0; 106   pos_ = 0;
107   } 107   }
108   108  
109   /// Return the number of bytes available for pulling. 109   /// Return the number of bytes available for pulling.
110   std::size_t 110   std::size_t
111   available() const noexcept 111   available() const noexcept
112   { 112   {
113   return data_.size() - pos_; 113   return data_.size() - pos_;
114   } 114   }
115   115  
116   /** Consume bytes from the source. 116   /** Consume bytes from the source.
117   117  
118   Advances the internal read position by the specified number 118   Advances the internal read position by the specified number
119   of bytes. The next call to @ref pull returns data starting 119   of bytes. The next call to @ref pull returns data starting
120   after the consumed bytes. 120   after the consumed bytes.
121   121  
122   @param n The number of bytes to consume. Must not exceed the 122   @param n The number of bytes to consume. Must not exceed the
123   total size of buffers returned by the previous @ref pull. 123   total size of buffers returned by the previous @ref pull.
124   */ 124   */
125   void 125   void
HITCBC 126   285 consume(std::size_t n) noexcept 126   285 consume(std::size_t n) noexcept
127   { 127   {
HITCBC 128   285 pos_ += n; 128   285 pos_ += n;
HITCBC 129   285 } 129   285 }
130   130  
131   /** Pull buffer data from the source. 131   /** Pull buffer data from the source.
132   132  
133   Fills the provided span with buffer descriptors pointing to 133   Fills the provided span with buffer descriptors pointing to
134   internal data starting from the current unconsumed position. 134   internal data starting from the current unconsumed position.
135   Returns a span of filled buffers. When no data remains, 135   Returns a span of filled buffers. When no data remains,
136   returns an empty span to signal completion. 136   returns an empty span to signal completion.
137   137  
138   Calling pull multiple times without intervening @ref consume 138   Calling pull multiple times without intervening @ref consume
139   returns the same data. Use consume to advance past processed 139   returns the same data. Use consume to advance past processed
140   bytes. 140   bytes.
141   141  
142   @param dest Span of capy::const_buffer to fill. 142   @param dest Span of capy::const_buffer to fill.
143   143  
144   @return An awaitable that await-returns `(error_code,std::span<capy::const_buffer>)`. 144   @return An awaitable that await-returns `(error_code,std::span<capy::const_buffer>)`.
145   145  
146   @par Cancellation 146   @par Cancellation
147   If the environment's stop token has been requested, the pull 147   If the environment's stop token has been requested, the pull
148   completes immediately with `capy::error::canceled` and an empty span. 148   completes immediately with `capy::error::canceled` and an empty span.
149   149  
150   @see consume, capy::test::fuse 150   @see consume, capy::test::fuse
151   */ 151   */
152   auto 152   auto
HITCBC 153   552 pull(std::span<capy::const_buffer> dest) 153   552 pull(std::span<capy::const_buffer> dest)
154   { 154   {
155   struct awaitable 155   struct awaitable
156   { 156   {
157   buffer_source* self_; 157   buffer_source* self_;
158   std::span<capy::const_buffer> dest_; 158   std::span<capy::const_buffer> dest_;
159   bool canceled_ = false; 159   bool canceled_ = false;
160   160  
HITCBC 161   552 bool await_ready() const noexcept { return false; } 161   552 bool await_ready() const noexcept { return false; }
162   162  
163   // The operation completes synchronously, but await_suspend is 163   // The operation completes synchronously, but await_suspend is
164   // the only place capy::io_env is delivered (the promise's 164   // the only place capy::io_env is delivered (the promise's
165   // transform_awaiter forwards it here). Returning false means 165   // transform_awaiter forwards it here). Returning false means
166   // the coroutine does not actually suspend; it resumes 166   // the coroutine does not actually suspend; it resumes
167   // immediately, having observed the stop token. See capy::io_env, 167   // immediately, having observed the stop token. See capy::io_env,
168   // IoAwaitable. 168   // IoAwaitable.
169   bool 169   bool
HITCBC 170   552 await_suspend( 170   552 await_suspend(
171   std::coroutine_handle<>, 171   std::coroutine_handle<>,
172   capy::io_env const* env) noexcept 172   capy::io_env const* env) noexcept
173   { 173   {
HITCBC 174   552 canceled_ = env->stop_token.stop_requested(); 174   552 canceled_ = env->stop_token.stop_requested();
HITCBC 175   552 return false; 175   552 return false;
176   } 176   }
177   177  
178   capy::io_result<std::span<capy::const_buffer>> 178   capy::io_result<std::span<capy::const_buffer>>
HITCBC 179   552 await_resume() 179   552 await_resume()
180   { 180   {
HITCBC 181   552 if(canceled_) 181   552 if(canceled_)
MISUBC 182   return {capy::error::canceled, {}}; 182   return {capy::error::canceled, {}};
183   183  
HITCBC 184   552 auto ec = self_->f_.maybe_fail(); 184   552 auto ec = self_->f_.maybe_fail();
HITCBC 185   467 if(ec) 185   467 if(ec)
HITCBC 186   85 return {ec, {}}; 186   85 return {ec, {}};
187   187  
HITCBC 188   382 if(self_->pos_ >= self_->data_.size()) 188   382 if(self_->pos_ >= self_->data_.size())
HITCBC 189   66 return {capy::error::eof, {}}; 189   66 return {capy::error::eof, {}};
190   190  
HITCBC 191   316 std::size_t avail = self_->data_.size() - self_->pos_; 191   316 std::size_t avail = self_->data_.size() - self_->pos_;
HITCBC 192   316 std::size_t to_return = (std::min)(avail, self_->max_pull_size_); 192   316 std::size_t to_return = (std::min)(avail, self_->max_pull_size_);
193   193  
HITCBC 194   316 if(dest_.empty()) 194   316 if(dest_.empty())
MISUBC 195   return {std::error_code(), {}}; 195   return {std::error_code(), {}};
196   196  
197   // Fill a single buffer descriptor 197   // Fill a single buffer descriptor
HITCBC 198   316 dest_[0] = capy::make_buffer( 198   316 dest_[0] = capy::make_buffer(
HITCBC 199   316 self_->data_.data() + self_->pos_, 199   316 self_->data_.data() + self_->pos_,
200   to_return); 200   to_return);
201   201  
HITCBC 202   316 return {std::error_code(), dest_.first(1)}; 202   316 return {std::error_code(), dest_.first(1)};
203   } 203   }
204   }; 204   };
HITCBC 205   552 return awaitable{this, dest}; 205   552 return awaitable{this, dest};
206   } 206   }
207   }; 207   };
208   208  
209   } // test 209   } // test
210   } // capy 210   } // capy
211   } // boost 211   } // boost
212   212  
213   #endif 213   #endif