100.00% Lines (36/36) 100.00% Functions (8/8)
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_STREAM_HPP 11   #ifndef BOOST_CAPY_TEST_READ_STREAM_HPP
11   #define BOOST_CAPY_TEST_READ_STREAM_HPP 12   #define BOOST_CAPY_TEST_READ_STREAM_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 <boost/capy/cond.hpp> 18   #include <boost/capy/cond.hpp>
18   #include <coroutine> 19   #include <coroutine>
19   #include <boost/capy/ex/io_env.hpp> 20   #include <boost/capy/ex/io_env.hpp>
20   #include <boost/capy/io_result.hpp> 21   #include <boost/capy/io_result.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 stream for testing read operations. 31   /** A mock stream for testing read operations.
31   32  
32   Use this to verify code that performs reads without needing 33   Use this to verify code that performs reads without needing
33   real I/O. Call @ref provide to supply data, then @ref read_some 34   real I/O. Call @ref provide to supply data, then @ref read_some
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. An optional `max_read_size` constructor 36   at controlled points. An optional `max_read_size` constructor
36   parameter limits bytes per read to simulate chunked delivery. 37   parameter limits bytes per read to simulate chunked delivery.
37   38  
38   This class satisfies the @ref ReadStream concept. 39   This class satisfies the @ref ReadStream concept.
39   40  
40   @par Thread Safety 41   @par Thread Safety
41   Not thread-safe. 42   Not thread-safe.
42   43  
43   @par Example 44   @par Example
44   @code 45   @code
45   fuse f; 46   fuse f;
46   read_stream rs( f ); 47   read_stream rs( f );
47   rs.provide( "Hello, " ); 48   rs.provide( "Hello, " );
48   rs.provide( "World!" ); 49   rs.provide( "World!" );
49   50  
50   auto r = f.armed( [&]( fuse& ) -> task<void> { 51   auto r = f.armed( [&]( fuse& ) -> task<void> {
51   char buf[32]; 52   char buf[32];
52   auto [ec, n] = co_await rs.read_some( 53   auto [ec, n] = co_await rs.read_some(
53   mutable_buffer( buf, sizeof( buf ) ) ); 54   mutable_buffer( buf, sizeof( buf ) ) );
54   if( ec ) 55   if( ec )
55   co_return; 56   co_return;
56   // buf contains "Hello, World!" 57   // buf contains "Hello, World!"
57   } ); 58   } );
58   @endcode 59   @endcode
59   60  
60   @see fuse, ReadStream 61   @see fuse, ReadStream
61   */ 62   */
62   class read_stream 63   class read_stream
63   { 64   {
64   fuse f_; 65   fuse f_;
65   std::string data_; 66   std::string data_;
66   std::size_t pos_ = 0; 67   std::size_t pos_ = 0;
67   std::size_t max_read_size_; 68   std::size_t max_read_size_;
68   69  
69   public: 70   public:
70   /** Construct a read stream. 71   /** Construct a read stream.
71   72  
72   @param f The fuse used to inject errors during reads. 73   @param f The fuse used to inject errors during reads.
73   74  
74   @param max_read_size Maximum bytes returned per read. 75   @param max_read_size Maximum bytes returned per read.
75   Use to simulate chunked network delivery. 76   Use to simulate chunked network delivery.
76   */ 77   */
HITCBC 77   1464 explicit read_stream( 78   1457 explicit read_stream(
78   fuse f = {}, 79   fuse f = {},
79   std::size_t max_read_size = std::size_t(-1)) noexcept 80   std::size_t max_read_size = std::size_t(-1)) noexcept
HITCBC 80   1464 : f_(std::move(f)) 81   1457 : f_(std::move(f))
HITCBC 81   1464 , max_read_size_(max_read_size) 82   1457 , max_read_size_(max_read_size)
82   { 83   {
HITCBC 83   1464 } 84   1457 }
84   85  
85   /** Append data to be returned by subsequent reads. 86   /** Append data to be returned by subsequent reads.
86   87  
87   Multiple calls accumulate data that @ref read_some returns. 88   Multiple calls accumulate data that @ref read_some returns.
88   89  
89   @param sv The data to append. 90   @param sv The data to append.
90   */ 91   */
91   void 92   void
HITCBC 92   1430 provide(std::string_view sv) 93   1423 provide(std::string_view sv)
93   { 94   {
HITCBC 94   1430 data_.append(sv); 95   1423 data_.append(sv);
HITCBC 95   1430 } 96   1423 }
96   97  
97   /// Clear all data and reset the read position. 98   /// Clear all data and reset the read position.
98   void 99   void
HITCBC 99   6 clear() noexcept 100   6 clear() noexcept
100   { 101   {
HITCBC 101   6 data_.clear(); 102   6 data_.clear();
HITCBC 102   6 pos_ = 0; 103   6 pos_ = 0;
HITCBC 103   6 } 104   6 }
104   105  
105   /// Return the number of bytes available for reading. 106   /// Return the number of bytes available for reading.
106   std::size_t 107   std::size_t
HITCBC 107   20 available() const noexcept 108   20 available() const noexcept
108   { 109   {
HITCBC 109   20 return data_.size() - pos_; 110   20 return data_.size() - pos_;
110   } 111   }
111   112  
112   /** Asynchronously read data from the stream. 113   /** Asynchronously read data from the stream.
113   114  
114   Transfers up to `buffer_size( buffers )` bytes from the internal 115   Transfers up to `buffer_size( buffers )` bytes from the internal
115   buffer to the provided mutable buffer sequence. If no data remains, 116   buffer to the provided mutable buffer sequence. If no data remains,
116   returns `error::eof`. Before every read, the attached @ref fuse is 117   returns `error::eof`. Before every read, the attached @ref fuse is
117   consulted to possibly inject an error for testing fault scenarios. 118   consulted to possibly inject an error for testing fault scenarios.
118   The returned `std::size_t` is the number of bytes transferred. 119   The returned `std::size_t` is the number of bytes transferred.
119   120  
120   @par Effects 121   @par Effects
121   On success, advances the internal read position by the number of 122   On success, advances the internal read position by the number of
122   bytes copied. If an error is injected by the fuse, the read position 123   bytes copied. If an error is injected by the fuse, the read position
123   remains unchanged. 124   remains unchanged.
124   125  
125   @par Exception Safety 126   @par Exception Safety
126   No-throw guarantee. 127   No-throw guarantee.
127   128  
  129 + @par Cancellation
  130 + If the environment's stop token has been requested, the read
  131 + completes immediately with `error::canceled` and transfers no
  132 + data. This lets code under test exercise its cancellation paths.
  133 + An empty buffer sequence is a no-op that completes successfully
  134 + regardless of the stop token.
  135 +
128   @param buffers The mutable buffer sequence to receive data. 136   @param buffers The mutable buffer sequence to receive data.
129   137  
130   @return An awaitable that await-returns `(error_code,std::size_t)`. 138   @return An awaitable that await-returns `(error_code,std::size_t)`.
131   139  
132   @see fuse 140   @see fuse
133   */ 141   */
134   template<MutableBufferSequence MB> 142   template<MutableBufferSequence MB>
135   auto 143   auto
HITCBC 136   1815 read_some(MB buffers) 144   1804 read_some(MB buffers)
137   { 145   {
138   struct awaitable 146   struct awaitable
139   { 147   {
140   read_stream* self_; 148   read_stream* self_;
141   MB buffers_; 149   MB buffers_;
  150 + bool canceled_ = false;
142   151  
HITCBC 143 - 1815 bool await_ready() const noexcept { return true; } 152 + 1804 bool await_ready() const noexcept { return false; }
144   153  
145 - // This method is required to satisfy Capy's IoAwaitable concept, 154 + // The operation completes synchronously, but await_suspend
146 - // but is never called because await_ready() returns true. 155 + // is the only place io_env is delivered (the promise's
147 - // 156 + // transform_awaiter forwards it here). Returning false means
148 - // Capy uses a two-layer awaitable system: the promise's 157 + // the coroutine does not actually suspend — it resumes
149 - // await_transform wraps awaitables in a transform_awaiter whose 158 + // immediately — so the read still completes synchronously
150 - // standard await_suspend(coroutine_handle) calls this custom 159 + // while having observed the stop token. See io_env, IoAwaitable.
151 - // 2-argument overload, passing the io_env from the coroutine's 160 + bool
HITGIC 152 - // context. For synchronous test awaitables like this one, the 161 + 1804 await_suspend(
153 - // coroutine never suspends, so this is not invoked. The signature  
154 - // exists to allow the same awaitable type to work with both  
155 - // synchronous (test) and asynchronous (real I/O) code.  
DUB 156 - void await_suspend(  
157   std::coroutine_handle<>, 162   std::coroutine_handle<>,
158 - io_env const*) const noexcept 163 + io_env const* env) noexcept
159   { 164   {
HITGNC   165 + 1804 canceled_ = env->stop_token.stop_requested();
HITGNC   166 + 1804 return false;
EUB 160   } 167   }
161   168  
162   io_result<std::size_t> 169   io_result<std::size_t>
HITCBC 163   1815 await_resume() 170   1804 await_resume()
164   { 171   {
165   // Empty buffer is a no-op regardless of 172   // Empty buffer is a no-op regardless of
166 - // stream state or fuse. 173 + // stream state, stop token, or fuse.
HITCBC 167   1815 if(buffer_empty(buffers_)) 174   1804 if(buffer_empty(buffers_))
HITCBC 168   7 return {{}, 0}; 175   7 return {{}, 0};
  176 +
HITGNC   177 + 1797 if(canceled_)
HITGNC   178 + 1 return {error::canceled, 0};
169   179  
HITCBC 170   1808 auto ec = self_->f_.maybe_fail(); 180   1796 auto ec = self_->f_.maybe_fail();
HITCBC 171   1572 if(ec) 181   1563 if(ec)
HITCBC 172   236 return {ec, 0}; 182   233 return {ec, 0};
173   183  
HITCBC 174   1336 if(self_->pos_ >= self_->data_.size()) 184   1330 if(self_->pos_ >= self_->data_.size())
HITCBC 175   115 return {error::eof, 0}; 185   115 return {error::eof, 0};
176   186  
HITCBC 177   1221 std::size_t avail = self_->data_.size() - self_->pos_; 187   1215 std::size_t avail = self_->data_.size() - self_->pos_;
HITCBC 178   1221 if(avail > self_->max_read_size_) 188   1215 if(avail > self_->max_read_size_)
HITCBC 179   282 avail = self_->max_read_size_; 189   276 avail = self_->max_read_size_;
HITCBC 180   1221 auto src = make_buffer(self_->data_.data() + self_->pos_, avail); 190   1215 auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
HITCBC 181   1221 std::size_t const n = buffer_copy(buffers_, src); 191   1215 std::size_t const n = buffer_copy(buffers_, src);
HITCBC 182   1221 self_->pos_ += n; 192   1215 self_->pos_ += n;
HITCBC 183   1221 return {{}, n}; 193   1215 return {{}, n};
184   } 194   }
185   }; 195   };
HITCBC 186   1815 return awaitable{this, buffers}; 196   1804 return awaitable{this, buffers};
187   } 197   }
188   }; 198   };
189   199  
190   } // test 200   } // test
191   } // capy 201   } // capy
192   } // boost 202   } // boost
193   203  
194   #endif 204   #endif