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