LCOV - code coverage report
Current view: top level - capy/test - read_stream.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 93.9 % 33 31 2
Test Date: 2026-02-17 18:14:47 Functions: 79.2 % 24 19 5

           TLA  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 HIT        1412 :     explicit read_stream(
      78                 :         fuse f = {},
      79                 :         std::size_t max_read_size = std::size_t(-1)) noexcept
      80            1412 :         : f_(std::move(f))
      81            1412 :         , max_read_size_(max_read_size)
      82                 :     {
      83            1412 :     }
      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            1380 :     provide(std::string_view sv)
      93                 :     {
      94            1380 :         data_.append(sv);
      95            1380 :     }
      96                 : 
      97                 :     /// Clear all data and reset the read position.
      98                 :     void
      99               6 :     clear() noexcept
     100                 :     {
     101               6 :         data_.clear();
     102               6 :         pos_ = 0;
     103               6 :     }
     104                 : 
     105                 :     /// Return the number of bytes available for reading.
     106                 :     std::size_t
     107              20 :     available() const noexcept
     108                 :     {
     109              20 :         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            1703 :     read_some(MB buffers)
     137                 :     {
     138                 :         struct awaitable
     139                 :         {
     140                 :             read_stream* self_;
     141                 :             MB buffers_;
     142                 : 
     143            1703 :             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 MIS           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 HIT        1703 :             await_resume()
     164                 :             {
     165                 :                 // Empty buffer is a no-op regardless of
     166                 :                 // stream state or fuse.
     167            1703 :                 if(buffer_empty(buffers_))
     168               7 :                     return {{}, 0};
     169                 : 
     170            1696 :                 auto ec = self_->f_.maybe_fail();
     171            1469 :                 if(ec)
     172             227 :                     return {ec, 0};
     173                 : 
     174            1242 :                 if(self_->pos_ >= self_->data_.size())
     175              89 :                     return {error::eof, 0};
     176                 : 
     177            1153 :                 std::size_t avail = self_->data_.size() - self_->pos_;
     178            1153 :                 if(avail > self_->max_read_size_)
     179             248 :                     avail = self_->max_read_size_;
     180            1153 :                 auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
     181            1153 :                 std::size_t const n = buffer_copy(buffers_, src);
     182            1153 :                 self_->pos_ += n;
     183            1153 :                 return {{}, n};
     184                 :             }
     185                 :         };
     186            1703 :         return awaitable{this, buffers};
     187                 :     }
     188                 : };
     189                 : 
     190                 : } // test
     191                 : } // capy
     192                 : } // boost
     193                 : 
     194                 : #endif
        

Generated by: LCOV version 2.3