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

Generated by: LCOV version 2.3