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