LCOV - code coverage report
Current view: top level - capy/test - read_source.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 85.7 % 49 42
Test Date: 2026-02-07 18:59:16 Functions: 78.9 % 19 15

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

Generated by: LCOV version 2.3