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   // 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/capy 8   // Official repository: https://github.com/cppalliance/capy
9   // 9   //
10   10  
11   #ifndef BOOST_CAPY_TEST_READ_STREAM_HPP 11   #ifndef BOOST_CAPY_TEST_READ_STREAM_HPP
12   #define BOOST_CAPY_TEST_READ_STREAM_HPP 12   #define BOOST_CAPY_TEST_READ_STREAM_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/buffer_copy.hpp> 16   #include <boost/capy/buffers/buffer_copy.hpp>
17   #include <boost/capy/buffers/make_buffer.hpp> 17   #include <boost/capy/buffers/make_buffer.hpp>
18   #include <boost/capy/cond.hpp> 18   #include <boost/capy/cond.hpp>
19   #include <coroutine> 19   #include <coroutine>
20   #include <boost/capy/ex/io_env.hpp> 20   #include <boost/capy/ex/io_env.hpp>
21   #include <boost/capy/io_result.hpp> 21   #include <boost/capy/io_result.hpp>
22   #include <boost/capy/test/fuse.hpp> 22   #include <boost/capy/test/fuse.hpp>
23   23  
24   #include <string> 24   #include <string>
25   #include <string_view> 25   #include <string_view>
26   26  
27   namespace boost { 27   namespace boost {
28   namespace capy { 28   namespace capy {
29   namespace test { 29   namespace test {
30   30  
31   /** Buffers data supplied via `provide`, then hands it out through `read_some`. 31   /** Buffers data supplied via `provide`, then hands it out through `read_some`.
32   32  
33   Use this to verify code that performs reads without needing 33   Use this to verify code that performs reads without needing
34   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
35   to consume it. The associated @ref fuse enables error injection 35   to consume it. The associated @ref fuse enables error injection
36   at controlled points. An optional `max_read_size` constructor 36   at controlled points. An optional `max_read_size` constructor
37   parameter limits bytes per read to simulate chunked delivery. 37   parameter limits bytes per read to simulate chunked delivery.
38   38  
39   This class satisfies the @ref ReadStream concept. 39   This class satisfies the @ref ReadStream concept.
40   40  
41   @par Thread Safety 41   @par Thread Safety
42   Not thread-safe. 42   Not thread-safe.
43   43  
44   @par Example 44   @par Example
45   @par !example example 45   @par !example example
46   46  
47   47  
48   @see fuse, ReadStream 48   @see fuse, ReadStream
49   */ 49   */
50   class read_stream 50   class read_stream
51   { 51   {
52   fuse f_; 52   fuse f_;
53   std::string data_; 53   std::string data_;
54   std::size_t pos_ = 0; 54   std::size_t pos_ = 0;
55   std::size_t max_read_size_; 55   std::size_t max_read_size_;
56   56  
57   public: 57   public:
58   /** Construct a read stream. 58   /** Construct a read stream.
59   59  
60   @param f The fuse used to inject errors during reads. 60   @param f The fuse used to inject errors during reads.
61   61  
62   @param max_read_size Maximum bytes returned per read. 62   @param max_read_size Maximum bytes returned per read.
63   Use to simulate chunked network delivery. 63   Use to simulate chunked network delivery.
64   */ 64   */
HITCBC 65   305 explicit read_stream( 65   305 explicit read_stream(
66   fuse f = {}, 66   fuse f = {},
67   std::size_t max_read_size = std::size_t(-1)) noexcept 67   std::size_t max_read_size = std::size_t(-1)) noexcept
HITCBC 68   305 : f_(std::move(f)) 68   305 : f_(std::move(f))
HITCBC 69   305 , max_read_size_(max_read_size) 69   305 , max_read_size_(max_read_size)
70   { 70   {
HITCBC 71   305 } 71   305 }
72   72  
73   /** Append data to be returned by subsequent reads. 73   /** Append data to be returned by subsequent reads.
74   74  
75   Multiple calls accumulate data that @ref read_some returns. 75   Multiple calls accumulate data that @ref read_some returns.
76   76  
77   @param sv The data to append. 77   @param sv The data to append.
78   */ 78   */
79   void 79   void
HITCBC 80   307 provide(std::string_view sv) 80   307 provide(std::string_view sv)
81   { 81   {
HITCBC 82   307 data_.append(sv); 82   307 data_.append(sv);
HITCBC 83   307 } 83   307 }
84   84  
85   /// Clear all data and reset the read position. 85   /// Clear all data and reset the read position.
86   void 86   void
HITCBC 87   6 clear() noexcept 87   6 clear() noexcept
88   { 88   {
HITCBC 89   6 data_.clear(); 89   6 data_.clear();
HITCBC 90   6 pos_ = 0; 90   6 pos_ = 0;
HITCBC 91   6 } 91   6 }
92   92  
93   /** Return the number of bytes available for reading. 93   /** Return the number of bytes available for reading.
94   94  
95   @return The number of provided bytes not yet consumed. 95   @return The number of provided bytes not yet consumed.
96   */ 96   */
97   std::size_t 97   std::size_t
HITCBC 98   24 available() const noexcept 98   24 available() const noexcept
99   { 99   {
HITCBC 100   24 return data_.size() - pos_; 100   24 return data_.size() - pos_;
101   } 101   }
102   102  
103   /** Asynchronously read data from the stream. 103   /** Asynchronously read data from the stream.
104   104  
105   Transfers up to `buffer_size( buffers )` bytes from the internal 105   Transfers up to `buffer_size( buffers )` bytes from the internal
106   buffer to the provided mutable buffer sequence. If no data remains, 106   buffer to the provided mutable buffer sequence. If no data remains,
107   returns `error::eof`. Before every read, the attached @ref fuse is 107   returns `error::eof`. Before every read, the attached @ref fuse is
108   consulted to possibly inject an error for testing fault scenarios. 108   consulted to possibly inject an error for testing fault scenarios.
109   The returned `std::size_t` is the number of bytes transferred. 109   The returned `std::size_t` is the number of bytes transferred.
110   110  
111   @par Effects 111   @par Effects
112   On success, advances the internal read position by the number of 112   On success, advances the internal read position by the number of
113   bytes copied. If an error is injected by the fuse, the read position 113   bytes copied. If an error is injected by the fuse, the read position
114   remains unchanged. 114   remains unchanged.
115   115  
116   @par Exception Safety 116   @par Exception Safety
117   Injected I/O conditions are reported via the `error_code` 117   Injected I/O conditions are reported via the `error_code`
118   component of the result. Throws `std::system_error` only when 118   component of the result. Throws `std::system_error` only when
119   the attached @ref fuse is in exception mode and reaches its 119   the attached @ref fuse is in exception mode and reaches its
120   failure point; no-throw otherwise. 120   failure point; no-throw otherwise.
121   121  
122   @par Cancellation 122   @par Cancellation
123   If the environment's stop token is requested, the read 123   If the environment's stop token is requested, the read
124   completes immediately with `error::canceled` and transfers no 124   completes immediately with `error::canceled` and transfers no
125   data. This lets code under test exercise its cancellation paths. 125   data. This lets code under test exercise its cancellation paths.
126   An empty buffer sequence is a no-op that completes successfully 126   An empty buffer sequence is a no-op that completes successfully
127   regardless of the stop token. 127   regardless of the stop token.
128   128  
129   @param buffers The mutable buffer sequence to receive data. 129   @param buffers The mutable buffer sequence to receive data.
130   130  
131   @return An awaitable that await-returns `(error_code,std::size_t)`. 131   @return An awaitable that await-returns `(error_code,std::size_t)`.
132   132  
133   @throws std::system_error When the attached @ref fuse is in 133   @throws std::system_error When the attached @ref fuse is in
134   exception mode and reaches its failure point. 134   exception mode and reaches its failure point.
135   135  
136   @see fuse 136   @see fuse
137   */ 137   */
138   template<MutableBufferSequence MB> 138   template<MutableBufferSequence MB>
139   auto 139   auto
HITCBC 140   430 read_some(MB buffers) 140   430 read_some(MB buffers)
141   { 141   {
142   struct awaitable 142   struct awaitable
143   { 143   {
144   read_stream* self_; 144   read_stream* self_;
145   MB buffers_; 145   MB buffers_;
146   bool canceled_ = false; 146   bool canceled_ = false;
147   147  
HITCBC 148   430 bool await_ready() const noexcept { return false; } 148   430 bool await_ready() const noexcept { return false; }
149   149  
150   // The operation completes synchronously, but await_suspend 150   // The operation completes synchronously, but await_suspend
151   // is the only place io_env is delivered (the promise's 151   // is the only place io_env is delivered (the promise's
152   // transform_awaiter forwards it here). Returning false means 152   // transform_awaiter forwards it here). Returning false means
153   // the coroutine does not actually suspend — it resumes 153   // the coroutine does not actually suspend — it resumes
154   // immediately — so the read still completes synchronously 154   // immediately — so the read still completes synchronously
155   // while having observed the stop token. See io_env, IoAwaitable. 155   // while having observed the stop token. See io_env, IoAwaitable.
156   bool 156   bool
HITCBC 157   430 await_suspend( 157   430 await_suspend(
158   std::coroutine_handle<>, 158   std::coroutine_handle<>,
159   io_env const* env) noexcept 159   io_env const* env) noexcept
160   { 160   {
HITCBC 161   430 canceled_ = env->stop_token.stop_requested(); 161   430 canceled_ = env->stop_token.stop_requested();
HITCBC 162   430 return false; 162   430 return false;
163   } 163   }
164   164  
165   [[nodiscard]] io_result<std::size_t> 165   [[nodiscard]] io_result<std::size_t>
HITCBC 166   430 await_resume() 166   430 await_resume()
167   { 167   {
168   // Empty buffer is a no-op regardless of 168   // Empty buffer is a no-op regardless of
169   // stream state, stop token, or fuse. 169   // stream state, stop token, or fuse.
HITCBC 170   430 if(buffer_empty(buffers_)) 170   430 if(buffer_empty(buffers_))
HITCBC 171   7 return {std::error_code(), 0}; 171   7 return {std::error_code(), 0};
172   172  
HITCBC 173   423 if(canceled_) 173   423 if(canceled_)
HITCBC 174   2 return {error::canceled, 0}; 174   2 return {error::canceled, 0};
175   175  
HITCBC 176   421 auto ec = self_->f_.maybe_fail(); 176   421 auto ec = self_->f_.maybe_fail();
HITCBC 177   330 if(ec) 177   330 if(ec)
HITCBC 178   91 return {ec, 0}; 178   91 return {ec, 0};
179   179  
HITCBC 180   239 if(self_->pos_ >= self_->data_.size()) 180   239 if(self_->pos_ >= self_->data_.size())
HITCBC 181   37 return {error::eof, 0}; 181   37 return {error::eof, 0};
182   182  
HITCBC 183   202 std::size_t avail = self_->data_.size() - self_->pos_; 183   202 std::size_t avail = self_->data_.size() - self_->pos_;
HITCBC 184   202 if(avail > self_->max_read_size_) 184   202 if(avail > self_->max_read_size_)
HITCBC 185   24 avail = self_->max_read_size_; 185   24 avail = self_->max_read_size_;
HITCBC 186   202 auto src = make_buffer(self_->data_.data() + self_->pos_, avail); 186   202 auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
HITCBC 187   202 std::size_t const n = buffer_copy(buffers_, src); 187   202 std::size_t const n = buffer_copy(buffers_, src);
HITCBC 188   202 self_->pos_ += n; 188   202 self_->pos_ += n;
HITCBC 189   202 return {std::error_code(), n}; 189   202 return {std::error_code(), n};
190   } 190   }
191   }; 191   };
HITCBC 192   430 return awaitable{this, buffers}; 192   430 return awaitable{this, buffers};
193   } 193   }
194   }; 194   };
195   195  
196   } // test 196   } // test
197   } // capy 197   } // capy
198   } // boost 198   } // boost
199   199  
200   #endif 200   #endif