100.00% Lines (37/37) 100.00% Functions (9/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 + // Copyright (c) 2026 Michael Vandeberg
3   // 4   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // 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) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 7   //
7   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
8   // 9   //
9   10  
10   #ifndef BOOST_CAPY_TEST_BUFFER_SOURCE_HPP 11   #ifndef BOOST_CAPY_TEST_BUFFER_SOURCE_HPP
11   #define BOOST_CAPY_TEST_BUFFER_SOURCE_HPP 12   #define BOOST_CAPY_TEST_BUFFER_SOURCE_HPP
12   13  
13   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/buffers.hpp> 15   #include <boost/capy/buffers.hpp>
15   #include <boost/capy/buffers/make_buffer.hpp> 16   #include <boost/capy/buffers/make_buffer.hpp>
16   #include <coroutine> 17   #include <coroutine>
17   #include <boost/capy/error.hpp> 18   #include <boost/capy/error.hpp>
18   #include <boost/capy/ex/io_env.hpp> 19   #include <boost/capy/ex/io_env.hpp>
19   #include <boost/capy/io_result.hpp> 20   #include <boost/capy/io_result.hpp>
20   #include <boost/capy/test/fuse.hpp> 21   #include <boost/capy/test/fuse.hpp>
21   22  
22   #include <algorithm> 23   #include <algorithm>
23   #include <span> 24   #include <span>
24   #include <string> 25   #include <string>
25   #include <string_view> 26   #include <string_view>
26   27  
27   namespace boost { 28   namespace boost {
28   namespace capy { 29   namespace capy {
29   namespace test { 30   namespace test {
30   31  
31   /** A mock buffer source for testing push operations. 32   /** A mock buffer source for testing push operations.
32   33  
33   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
34   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,
35   then @ref pull to retrieve buffer descriptors. The associated 36   then @ref pull to retrieve buffer descriptors. The associated
36   @ref fuse enables error injection at controlled points. 37   @ref fuse enables error injection at controlled points.
37   38  
38   This class satisfies the @ref BufferSource concept by providing 39   This class satisfies the @ref BufferSource concept by providing
39   a pull interface that fills an array of buffer descriptors and 40   a pull interface that fills an array of buffer descriptors and
40   a consume interface to indicate bytes used. 41   a consume interface to indicate bytes used.
41   42  
42   @par Thread Safety 43   @par Thread Safety
43   Not thread-safe. 44   Not thread-safe.
44   45  
45   @par Example 46   @par Example
46   @code 47   @code
47   fuse f; 48   fuse f;
48   buffer_source bs( f ); 49   buffer_source bs( f );
49   bs.provide( "Hello, " ); 50   bs.provide( "Hello, " );
50   bs.provide( "World!" ); 51   bs.provide( "World!" );
51   52  
52   auto r = f.armed( [&]( fuse& ) -> task<void> { 53   auto r = f.armed( [&]( fuse& ) -> task<void> {
53   const_buffer arr[16]; 54   const_buffer arr[16];
54   auto [ec, bufs] = co_await bs.pull( arr ); 55   auto [ec, bufs] = co_await bs.pull( arr );
55   if( ec ) 56   if( ec )
56   co_return; 57   co_return;
57   // bufs contains buffer descriptors 58   // bufs contains buffer descriptors
58   std::size_t n = buffer_size( bufs ); 59   std::size_t n = buffer_size( bufs );
59   bs.consume( n ); 60   bs.consume( n );
60   } ); 61   } );
61   @endcode 62   @endcode
62   63  
63   @see fuse, BufferSource 64   @see fuse, BufferSource
64   */ 65   */
65   class buffer_source 66   class buffer_source
66   { 67   {
67   fuse f_; 68   fuse f_;
68   std::string data_; 69   std::string data_;
69   std::size_t pos_ = 0; 70   std::size_t pos_ = 0;
70   std::size_t max_pull_size_; 71   std::size_t max_pull_size_;
71   72  
72   public: 73   public:
73   /** Construct a buffer source. 74   /** Construct a buffer source.
74   75  
75   @param f The fuse used to inject errors during pulls. 76   @param f The fuse used to inject errors during pulls.
76   77  
77   @param max_pull_size Maximum bytes returned per pull. 78   @param max_pull_size Maximum bytes returned per pull.
78   Use to simulate chunked delivery. 79   Use to simulate chunked delivery.
79   */ 80   */
HITCBC 80   376 explicit buffer_source( 81   377 explicit buffer_source(
81   fuse f = {}, 82   fuse f = {},
82   std::size_t max_pull_size = std::size_t(-1)) noexcept 83   std::size_t max_pull_size = std::size_t(-1)) noexcept
HITCBC 83   376 : f_(std::move(f)) 84   377 : f_(std::move(f))
HITCBC 84   376 , max_pull_size_(max_pull_size) 85   377 , max_pull_size_(max_pull_size)
85   { 86   {
HITCBC 86   376 } 87   377 }
87   88  
88   /** Append data to be returned by subsequent pulls. 89   /** Append data to be returned by subsequent pulls.
89   90  
90   Multiple calls accumulate data that @ref pull returns. 91   Multiple calls accumulate data that @ref pull returns.
91   92  
92   @param sv The data to append. 93   @param sv The data to append.
93   */ 94   */
94   void 95   void
HITCBC 95   388 provide(std::string_view sv) 96   389 provide(std::string_view sv)
96   { 97   {
HITCBC 97   388 data_.append(sv); 98   389 data_.append(sv);
HITCBC 98   388 } 99   389 }
99   100  
100   /// Clear all data and reset the read position. 101   /// Clear all data and reset the read position.
101   void 102   void
HITCBC 102   6 clear() noexcept 103   6 clear() noexcept
103   { 104   {
HITCBC 104   6 data_.clear(); 105   6 data_.clear();
HITCBC 105   6 pos_ = 0; 106   6 pos_ = 0;
HITCBC 106   6 } 107   6 }
107   108  
108   /// Return the number of bytes available for pulling. 109   /// Return the number of bytes available for pulling.
109   std::size_t 110   std::size_t
HITCBC 110   18 available() const noexcept 111   18 available() const noexcept
111   { 112   {
HITCBC 112   18 return data_.size() - pos_; 113   18 return data_.size() - pos_;
113   } 114   }
114   115  
115   /** Consume bytes from the source. 116   /** Consume bytes from the source.
116   117  
117   Advances the internal read position by the specified number 118   Advances the internal read position by the specified number
118   of bytes. The next call to @ref pull returns data starting 119   of bytes. The next call to @ref pull returns data starting
119   after the consumed bytes. 120   after the consumed bytes.
120   121  
121   @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
122   total size of buffers returned by the previous @ref pull. 123   total size of buffers returned by the previous @ref pull.
123   */ 124   */
124   void 125   void
HITCBC 125   319 consume(std::size_t n) noexcept 126   319 consume(std::size_t n) noexcept
126   { 127   {
HITCBC 127   319 pos_ += n; 128   319 pos_ += n;
HITCBC 128   319 } 129   319 }
129   130  
130   /** Pull buffer data from the source. 131   /** Pull buffer data from the source.
131   132  
132   Fills the provided span with buffer descriptors pointing to 133   Fills the provided span with buffer descriptors pointing to
133   internal data starting from the current unconsumed position. 134   internal data starting from the current unconsumed position.
134   Returns a span of filled buffers. When no data remains, 135   Returns a span of filled buffers. When no data remains,
135   returns an empty span to signal completion. 136   returns an empty span to signal completion.
136   137  
137   Calling pull multiple times without intervening @ref consume 138   Calling pull multiple times without intervening @ref consume
138   returns the same data. Use consume to advance past processed 139   returns the same data. Use consume to advance past processed
139   bytes. 140   bytes.
140   141  
141   @param dest Span of const_buffer to fill. 142   @param dest Span of const_buffer to fill.
142   143  
143   @return An awaitable that await-returns `(error_code,std::span<const_buffer>)`. 144   @return An awaitable that await-returns `(error_code,std::span<const_buffer>)`.
144   145  
  146 + @par Cancellation
  147 + If the environment's stop token has been requested, the pull
  148 + completes immediately with `error::canceled` and an empty span.
  149 +
145   @see consume, fuse 150   @see consume, fuse
146   */ 151   */
147   auto 152   auto
HITCBC 148   656 pull(std::span<const_buffer> dest) 153   657 pull(std::span<const_buffer> dest)
149   { 154   {
150   struct awaitable 155   struct awaitable
151   { 156   {
152   buffer_source* self_; 157   buffer_source* self_;
153   std::span<const_buffer> dest_; 158   std::span<const_buffer> dest_;
  159 + bool canceled_ = false;
154   160  
HITCBC 155 - 656 bool await_ready() const noexcept { return true; } 161 + 657 bool await_ready() const noexcept { return false; }
156   162  
157 - // This method is required to satisfy Capy's IoAwaitable concept, 163 + // The operation completes synchronously, but await_suspend is
158 - // but is never called because await_ready() returns true. 164 + // the only place io_env is delivered (the promise's
159 - // 165 + // transform_awaiter forwards it here). Returning false means
160 - // Capy uses a two-layer awaitable system: the promise's 166 + // the coroutine does not actually suspend; it resumes
161 - // await_transform wraps awaitables in a transform_awaiter whose 167 + // immediately, having observed the stop token. See io_env,
162 - // standard await_suspend(coroutine_handle) calls this custom 168 + // IoAwaitable.
163 - // 2-argument overload, passing the io_env from the coroutine's 169 + bool
HITGIC 164 - // context. For synchronous test awaitables like this one, the 170 + 657 await_suspend(
165 - // coroutine never suspends, so this is not invoked. The signature  
166 - // exists to allow the same awaitable type to work with both  
167 - // synchronous (test) and asynchronous (real I/O) code.  
DUB 168 - void await_suspend(  
169   std::coroutine_handle<>, 171   std::coroutine_handle<>,
170 - io_env const*) const noexcept 172 + io_env const* env) noexcept
171   { 173   {
HITGNC   174 + 657 canceled_ = env->stop_token.stop_requested();
HITGNC   175 + 657 return false;
EUB 172   } 176   }
173   177  
174   io_result<std::span<const_buffer>> 178   io_result<std::span<const_buffer>>
HITCBC 175   656 await_resume() 179   657 await_resume()
176   { 180   {
HITGNC   181 + 657 if(canceled_)
HITGNC   182 + 1 return {error::canceled, {}};
  183 +
HITCBC 177   656 auto ec = self_->f_.maybe_fail(); 184   656 auto ec = self_->f_.maybe_fail();
HITCBC 178   546 if(ec) 185   546 if(ec)
HITCBC 179   110 return {ec, {}}; 186   110 return {ec, {}};
180   187  
HITCBC 181   436 if(self_->pos_ >= self_->data_.size()) 188   436 if(self_->pos_ >= self_->data_.size())
HITCBC 182   72 return {error::eof, {}}; 189   72 return {error::eof, {}};
183   190  
HITCBC 184   364 std::size_t avail = self_->data_.size() - self_->pos_; 191   364 std::size_t avail = self_->data_.size() - self_->pos_;
HITCBC 185   364 std::size_t to_return = (std::min)(avail, self_->max_pull_size_); 192   364 std::size_t to_return = (std::min)(avail, self_->max_pull_size_);
186   193  
HITCBC 187   364 if(dest_.empty()) 194   364 if(dest_.empty())
HITCBC 188   2 return {{}, {}}; 195   2 return {{}, {}};
189   196  
190   // Fill a single buffer descriptor 197   // Fill a single buffer descriptor
HITCBC 191   362 dest_[0] = make_buffer( 198   362 dest_[0] = make_buffer(
HITCBC 192   362 self_->data_.data() + self_->pos_, 199   362 self_->data_.data() + self_->pos_,
193   to_return); 200   to_return);
194   201  
HITCBC 195   362 return {{}, dest_.first(1)}; 202   362 return {{}, dest_.first(1)};
196   } 203   }
197   }; 204   };
HITCBC 198   656 return awaitable{this, dest}; 205   657 return awaitable{this, dest};
199   } 206   }
200   }; 207   };
201   208  
202   } // test 209   } // test
203   } // capy 210   } // capy
204   } // boost 211   } // boost
205   212  
206   #endif 213   #endif