LCOV - code coverage report
Current view: top level - capy/test - read_stream.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 93.1 % 29 27
Test Date: 2026-02-12 22:58:59 Functions: 78.3 % 23 18

            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_STREAM_HPP
      11              : #define BOOST_CAPY_TEST_READ_STREAM_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/cond.hpp>
      18              : #include <coroutine>
      19              : #include <boost/capy/ex/io_env.hpp>
      20              : #include <boost/capy/io_result.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 stream for testing read operations.
      31              : 
      32              :     Use this to verify code that performs reads without needing
      33              :     real I/O. Call @ref provide to supply data, then @ref read_some
      34              :     to consume it. The associated @ref fuse enables error injection
      35              :     at controlled points. An optional `max_read_size` constructor
      36              :     parameter limits bytes per read to simulate chunked delivery.
      37              : 
      38              :     This class satisfies the @ref ReadStream concept.
      39              : 
      40              :     @par Thread Safety
      41              :     Not thread-safe.
      42              : 
      43              :     @par Example
      44              :     @code
      45              :     fuse f;
      46              :     read_stream rs( f );
      47              :     rs.provide( "Hello, " );
      48              :     rs.provide( "World!" );
      49              : 
      50              :     auto r = f.armed( [&]( fuse& ) -> task<void> {
      51              :         char buf[32];
      52              :         auto [ec, n] = co_await rs.read_some(
      53              :             mutable_buffer( buf, sizeof( buf ) ) );
      54              :         if( ec )
      55              :             co_return;
      56              :         // buf contains "Hello, World!"
      57              :     } );
      58              :     @endcode
      59              : 
      60              :     @see fuse, ReadStream
      61              : */
      62              : class read_stream
      63              : {
      64              :     fuse f_;
      65              :     std::string data_;
      66              :     std::size_t pos_ = 0;
      67              :     std::size_t max_read_size_;
      68              : 
      69              : public:
      70              :     /** Construct a read stream.
      71              : 
      72              :         @param f The fuse used to inject errors during reads.
      73              : 
      74              :         @param max_read_size Maximum bytes returned per read.
      75              :         Use to simulate chunked network delivery.
      76              :     */
      77         1328 :     explicit read_stream(
      78              :         fuse f = {},
      79              :         std::size_t max_read_size = std::size_t(-1)) noexcept
      80         1328 :         : f_(std::move(f))
      81         1328 :         , max_read_size_(max_read_size)
      82              :     {
      83         1328 :     }
      84              : 
      85              :     /** Append data to be returned by subsequent reads.
      86              : 
      87              :         Multiple calls accumulate data that @ref read_some returns.
      88              : 
      89              :         @param sv The data to append.
      90              :     */
      91              :     void
      92         1300 :     provide(std::string_view sv)
      93              :     {
      94         1300 :         data_.append(sv);
      95         1300 :     }
      96              : 
      97              :     /// Clear all data and reset the read position.
      98              :     void
      99              :     clear() noexcept
     100              :     {
     101              :         data_.clear();
     102              :         pos_ = 0;
     103              :     }
     104              : 
     105              :     /// Return the number of bytes available for reading.
     106              :     std::size_t
     107            4 :     available() const noexcept
     108              :     {
     109            4 :         return data_.size() - pos_;
     110              :     }
     111              : 
     112              :     /** Asynchronously read data from the stream.
     113              : 
     114              :         Transfers up to `buffer_size( buffers )` bytes from the internal
     115              :         buffer to the provided mutable buffer sequence. If no data remains,
     116              :         returns `error::eof`. Before every read, the attached @ref fuse is
     117              :         consulted to possibly inject an error for testing fault scenarios.
     118              :         The returned `std::size_t` is the number of bytes transferred.
     119              : 
     120              :         @par Effects
     121              :         On success, advances the internal read position by the number of
     122              :         bytes copied. If an error is injected by the fuse, the read position
     123              :         remains unchanged.
     124              : 
     125              :         @par Exception Safety
     126              :         No-throw guarantee.
     127              : 
     128              :         @param buffers The mutable buffer sequence to receive data.
     129              : 
     130              :         @return An awaitable yielding `(error_code,std::size_t)`.
     131              : 
     132              :         @see fuse
     133              :     */
     134              :     template<MutableBufferSequence MB>
     135              :     auto
     136         1589 :     read_some(MB buffers)
     137              :     {
     138              :         struct awaitable
     139              :         {
     140              :             read_stream* self_;
     141              :             MB buffers_;
     142              : 
     143         1589 :             bool await_ready() const noexcept { return true; }
     144              : 
     145              :             // This method is required to satisfy Capy's IoAwaitable concept,
     146              :             // but is never called because await_ready() returns true.
     147              :             //
     148              :             // Capy uses a two-layer awaitable system: the promise's
     149              :             // await_transform wraps awaitables in a transform_awaiter whose
     150              :             // standard await_suspend(coroutine_handle) calls this custom
     151              :             // 2-argument overload, passing the io_env from the coroutine's
     152              :             // context. For synchronous test awaitables like this one, the
     153              :             // coroutine never suspends, so this is not invoked. The signature
     154              :             // exists to allow the same awaitable type to work with both
     155              :             // synchronous (test) and asynchronous (real I/O) code.
     156            0 :             void await_suspend(
     157              :                 std::coroutine_handle<>,
     158              :                 io_env const*) const noexcept
     159              :             {
     160            0 :             }
     161              : 
     162              :             io_result<std::size_t>
     163         1589 :             await_resume()
     164              :             {
     165              :                 // Empty buffer is a no-op regardless of
     166              :                 // stream state or fuse.
     167         1589 :                 if(buffer_empty(buffers_))
     168            3 :                     return {{}, 0};
     169              : 
     170         1586 :                 auto ec = self_->f_.maybe_fail();
     171         1386 :                 if(ec)
     172          200 :                     return {ec, 0};
     173              : 
     174         1186 :                 if(self_->pos_ >= self_->data_.size())
     175           85 :                     return {error::eof, 0};
     176              : 
     177         1101 :                 std::size_t avail = self_->data_.size() - self_->pos_;
     178         1101 :                 if(avail > self_->max_read_size_)
     179          236 :                     avail = self_->max_read_size_;
     180         1101 :                 auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
     181         1101 :                 std::size_t const n = buffer_copy(buffers_, src);
     182         1101 :                 self_->pos_ += n;
     183         1101 :                 return {{}, n};
     184              :             }
     185              :         };
     186         1589 :         return awaitable{this, buffers};
     187              :     }
     188              : };
     189              : 
     190              : } // test
     191              : } // capy
     192              : } // boost
     193              : 
     194              : #endif
        

Generated by: LCOV version 2.3