92.45% Lines (49/53) 83.33% Functions (10/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   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/capy 7   // Official repository: https://github.com/cppalliance/capy
8   // 8   //
9   9  
10   #ifndef BOOST_CAPY_TEST_READ_SOURCE_HPP 10   #ifndef BOOST_CAPY_TEST_READ_SOURCE_HPP
11   #define BOOST_CAPY_TEST_READ_SOURCE_HPP 11   #define BOOST_CAPY_TEST_READ_SOURCE_HPP
12   12  
13   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/buffers.hpp> 14   #include <boost/capy/buffers.hpp>
15   #include <boost/capy/buffers/buffer_copy.hpp> 15   #include <boost/capy/buffers/buffer_copy.hpp>
16   #include <boost/capy/buffers/make_buffer.hpp> 16   #include <boost/capy/buffers/make_buffer.hpp>
17   #include <coroutine> 17   #include <coroutine>
18   #include <boost/capy/ex/io_env.hpp> 18   #include <boost/capy/ex/io_env.hpp>
19   #include <boost/capy/io_result.hpp> 19   #include <boost/capy/io_result.hpp>
20   #include <boost/capy/error.hpp> 20   #include <boost/capy/error.hpp>
21   #include <boost/capy/test/fuse.hpp> 21   #include <boost/capy/test/fuse.hpp>
22   22  
23   #include <string> 23   #include <string>
24   #include <string_view> 24   #include <string_view>
25   25  
26   namespace boost { 26   namespace boost {
27   namespace capy { 27   namespace capy {
28   namespace test { 28   namespace test {
29   29  
30   /** A mock source for testing read operations. 30   /** A mock source for testing read operations.
31   31  
32   Use this to verify code that performs complete reads without needing 32   Use this to verify code that performs complete reads without needing
33   real I/O. Call @ref provide to supply data, then @ref read 33   real I/O. Call @ref provide to supply data, then @ref read
34   to consume it. The associated @ref fuse enables error injection 34   to consume it. The associated @ref fuse enables error injection
35   at controlled points. 35   at controlled points.
36   36  
37   This class satisfies the @ref ReadSource concept by providing both 37   This class satisfies the @ref ReadSource concept by providing both
38   partial reads via `read_some` (satisfying @ref ReadStream) and 38   partial reads via `read_some` (satisfying @ref ReadStream) and
39   complete reads via `read` that fill the entire buffer sequence 39   complete reads via `read` that fill the entire buffer sequence
40   before returning. 40   before returning.
41   41  
42   @par Thread Safety 42   @par Thread Safety
43   Not thread-safe. 43   Not thread-safe.
44   44  
45   @par Example 45   @par Example
46   @code 46   @code
47   fuse f; 47   fuse f;
48   read_source rs( f ); 48   read_source rs( f );
49   rs.provide( "Hello, " ); 49   rs.provide( "Hello, " );
50   rs.provide( "World!" ); 50   rs.provide( "World!" );
51   51  
52   auto r = f.armed( [&]( fuse& ) -> task<void> { 52   auto r = f.armed( [&]( fuse& ) -> task<void> {
53   char buf[32]; 53   char buf[32];
54   auto [ec, n] = co_await rs.read( 54   auto [ec, n] = co_await rs.read(
55   mutable_buffer( buf, sizeof( buf ) ) ); 55   mutable_buffer( buf, sizeof( buf ) ) );
56   if( ec ) 56   if( ec )
57   co_return; 57   co_return;
58   // buf contains "Hello, World!" 58   // buf contains "Hello, World!"
59   } ); 59   } );
60   @endcode 60   @endcode
61   61  
62   @see fuse, ReadSource 62   @see fuse, ReadSource
63   */ 63   */
64   class read_source 64   class read_source
65   { 65   {
66   fuse f_; 66   fuse f_;
67   std::string data_; 67   std::string data_;
68   std::size_t pos_ = 0; 68   std::size_t pos_ = 0;
69   std::size_t max_read_size_; 69   std::size_t max_read_size_;
70   70  
71   public: 71   public:
72   /** Construct a read source. 72   /** Construct a read source.
73   73  
74   @param f The fuse used to inject errors during reads. 74   @param f The fuse used to inject errors during reads.
75   75  
76   @param max_read_size Maximum bytes returned per read. 76   @param max_read_size Maximum bytes returned per read.
77   Use to simulate chunked delivery. 77   Use to simulate chunked delivery.
78   */ 78   */
HITCBC 79   447 explicit read_source( 79   447 explicit read_source(
80   fuse f = {}, 80   fuse f = {},
81   std::size_t max_read_size = std::size_t(-1)) noexcept 81   std::size_t max_read_size = std::size_t(-1)) noexcept
HITCBC 82   447 : f_(std::move(f)) 82   447 : f_(std::move(f))
HITCBC 83   447 , max_read_size_(max_read_size) 83   447 , max_read_size_(max_read_size)
84   { 84   {
HITCBC 85   447 } 85   447 }
86   86  
87   /** Append data to be returned by subsequent reads. 87   /** Append data to be returned by subsequent reads.
88   88  
89   Multiple calls accumulate data that @ref read returns. 89   Multiple calls accumulate data that @ref read returns.
90   90  
91   @param sv The data to append. 91   @param sv The data to append.
92   */ 92   */
93   void 93   void
HITCBC 94   404 provide(std::string_view sv) 94   404 provide(std::string_view sv)
95   { 95   {
HITCBC 96   404 data_.append(sv); 96   404 data_.append(sv);
HITCBC 97   404 } 97   404 }
98   98  
99   /// Clear all data and reset the read position. 99   /// Clear all data and reset the read position.
100   void 100   void
HITCBC 101   2 clear() noexcept 101   2 clear() noexcept
102   { 102   {
HITCBC 103   2 data_.clear(); 103   2 data_.clear();
HITCBC 104   2 pos_ = 0; 104   2 pos_ = 0;
HITCBC 105   2 } 105   2 }
106   106  
107   /// Return the number of bytes available for reading. 107   /// Return the number of bytes available for reading.
108   std::size_t 108   std::size_t
HITCBC 109   30 available() const noexcept 109   30 available() const noexcept
110   { 110   {
HITCBC 111   30 return data_.size() - pos_; 111   30 return data_.size() - pos_;
112   } 112   }
113   113  
114   /** Asynchronously read some data from the source. 114   /** Asynchronously read some data from the source.
115   115  
116   Transfers up to `buffer_size( buffers )` bytes from the internal 116   Transfers up to `buffer_size( buffers )` bytes from the internal
117   buffer to the provided mutable buffer sequence. If no data 117   buffer to the provided mutable buffer sequence. If no data
118   remains, returns `error::eof`. Before every read, the attached 118   remains, returns `error::eof`. Before every read, the attached
119   @ref fuse is consulted to possibly inject an error for testing 119   @ref fuse is consulted to possibly inject an error for testing
120   fault scenarios. 120   fault scenarios.
121   121  
122   @param buffers The mutable buffer sequence to receive data. 122   @param buffers The mutable buffer sequence to receive data.
123   123  
124   @return An awaitable that await-returns `(error_code,std::size_t)`. 124   @return An awaitable that await-returns `(error_code,std::size_t)`.
125   125  
126   @see fuse 126   @see fuse
127   */ 127   */
128   template<MutableBufferSequence MB> 128   template<MutableBufferSequence MB>
129   auto 129   auto
HITCBC 130   116 read_some(MB buffers) 130   116 read_some(MB buffers)
131   { 131   {
132   struct awaitable 132   struct awaitable
133   { 133   {
134   read_source* self_; 134   read_source* self_;
135   MB buffers_; 135   MB buffers_;
136   136  
HITCBC 137   116 bool await_ready() const noexcept { return true; } 137   116 bool await_ready() const noexcept { return true; }
138   138  
MISUBC 139   void await_suspend( 139   void await_suspend(
140   std::coroutine_handle<>, 140   std::coroutine_handle<>,
141   io_env const*) const noexcept 141   io_env const*) const noexcept
142   { 142   {
MISUBC 143   } 143   }
144   144  
145   io_result<std::size_t> 145   io_result<std::size_t>
HITCBC 146   116 await_resume() 146   116 await_resume()
147   { 147   {
HITCBC 148   116 if(buffer_empty(buffers_)) 148   116 if(buffer_empty(buffers_))
HITCBC 149   4 return {{}, 0}; 149   4 return {{}, 0};
150   150  
HITCBC 151   112 auto ec = self_->f_.maybe_fail(); 151   112 auto ec = self_->f_.maybe_fail();
HITCBC 152   80 if(ec) 152   80 if(ec)
HITCBC 153   32 return {ec, 0}; 153   32 return {ec, 0};
154   154  
HITCBC 155   48 if(self_->pos_ >= self_->data_.size()) 155   48 if(self_->pos_ >= self_->data_.size())
HITCBC 156   4 return {error::eof, 0}; 156   4 return {error::eof, 0};
157   157  
HITCBC 158   44 std::size_t avail = self_->data_.size() - self_->pos_; 158   44 std::size_t avail = self_->data_.size() - self_->pos_;
HITCBC 159   44 if(avail > self_->max_read_size_) 159   44 if(avail > self_->max_read_size_)
HITCBC 160   14 avail = self_->max_read_size_; 160   14 avail = self_->max_read_size_;
HITCBC 161   44 auto src = make_buffer(self_->data_.data() + self_->pos_, avail); 161   44 auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
HITCBC 162   44 std::size_t const n = buffer_copy(buffers_, src); 162   44 std::size_t const n = buffer_copy(buffers_, src);
HITCBC 163   44 self_->pos_ += n; 163   44 self_->pos_ += n;
HITCBC 164   44 return {{}, n}; 164   44 return {{}, n};
165   } 165   }
166   }; 166   };
HITCBC 167   116 return awaitable{this, buffers}; 167   116 return awaitable{this, buffers};
168   } 168   }
169   169  
170   /** Asynchronously read data from the source. 170   /** Asynchronously read data from the source.
171   171  
172   Fills the entire buffer sequence from the internal data. 172   Fills the entire buffer sequence from the internal data.
173   If the available data is less than the buffer size, returns 173   If the available data is less than the buffer size, returns
174   `error::eof` with the number of bytes transferred. Before 174   `error::eof` with the number of bytes transferred. Before
175   every read, the attached @ref fuse is consulted to possibly 175   every read, the attached @ref fuse is consulted to possibly
176   inject an error for testing fault scenarios. 176   inject an error for testing fault scenarios.
177   177  
178   Unlike @ref read_some, this ignores `max_read_size` and 178   Unlike @ref read_some, this ignores `max_read_size` and
179   transfers all available data in a single operation, matching 179   transfers all available data in a single operation, matching
180   the @ref ReadSource semantic contract. 180   the @ref ReadSource semantic contract.
181   181  
182   @param buffers The mutable buffer sequence to receive data. 182   @param buffers The mutable buffer sequence to receive data.
183   183  
184   @return An awaitable that await-returns `(error_code,std::size_t)`. 184   @return An awaitable that await-returns `(error_code,std::size_t)`.
185   185  
186   @see fuse 186   @see fuse
187   */ 187   */
188   template<MutableBufferSequence MB> 188   template<MutableBufferSequence MB>
189   auto 189   auto
HITCBC 190   438 read(MB buffers) 190   438 read(MB buffers)
191   { 191   {
192   struct awaitable 192   struct awaitable
193   { 193   {
194   read_source* self_; 194   read_source* self_;
195   MB buffers_; 195   MB buffers_;
196   196  
HITCBC 197   438 bool await_ready() const noexcept { return true; } 197   438 bool await_ready() const noexcept { return true; }
198   198  
MISUBC 199   void await_suspend( 199   void await_suspend(
200   std::coroutine_handle<>, 200   std::coroutine_handle<>,
201   io_env const*) const noexcept 201   io_env const*) const noexcept
202   { 202   {
MISUBC 203   } 203   }
204   204  
205   io_result<std::size_t> 205   io_result<std::size_t>
HITCBC 206   438 await_resume() 206   438 await_resume()
207   { 207   {
HITCBC 208   438 if(buffer_empty(buffers_)) 208   438 if(buffer_empty(buffers_))
HITCBC 209   2 return {{}, 0}; 209   2 return {{}, 0};
210   210  
HITCBC 211   436 auto ec = self_->f_.maybe_fail(); 211   436 auto ec = self_->f_.maybe_fail();
HITCBC 212   338 if(ec) 212   338 if(ec)
HITCBC 213   98 return {ec, 0}; 213   98 return {ec, 0};
214   214  
HITCBC 215   240 if(self_->pos_ >= self_->data_.size()) 215   240 if(self_->pos_ >= self_->data_.size())
HITCBC 216   22 return {error::eof, 0}; 216   22 return {error::eof, 0};
217   217  
HITCBC 218   218 std::size_t avail = self_->data_.size() - self_->pos_; 218   218 std::size_t avail = self_->data_.size() - self_->pos_;
HITCBC 219   218 auto src = make_buffer(self_->data_.data() + self_->pos_, avail); 219   218 auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
HITCBC 220   218 std::size_t const n = buffer_copy(buffers_, src); 220   218 std::size_t const n = buffer_copy(buffers_, src);
HITCBC 221   218 self_->pos_ += n; 221   218 self_->pos_ += n;
222   222  
HITCBC 223   218 if(n < buffer_size(buffers_)) 223   218 if(n < buffer_size(buffers_))
HITCBC 224   84 return {error::eof, n}; 224   84 return {error::eof, n};
HITCBC 225   134 return {{}, n}; 225   134 return {{}, n};
226   } 226   }
227   }; 227   };
HITCBC 228   438 return awaitable{this, buffers}; 228   438 return awaitable{this, buffers};
229   } 229   }
230   }; 230   };
231   231  
232   } // test 232   } // test
233   } // capy 233   } // capy
234   } // boost 234   } // boost
235   235  
236   #endif 236   #endif