LCOV - code coverage report
Current view: top level - capy/test - read_stream.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 36 36
Test Date: 2026-08-21 22:12:46 Functions: 100.0 % 24 24

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       3                 : // Copyright (c) 2026 Michael Vandeberg
       4                 : //
       5                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       6                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       7                 : //
       8                 : // Official repository: https://github.com/cppalliance/capy
       9                 : //
      10                 : 
      11                 : #ifndef BOOST_CAPY_TEST_READ_STREAM_HPP
      12                 : #define BOOST_CAPY_TEST_READ_STREAM_HPP
      13                 : 
      14                 : #include <boost/capy/detail/config.hpp>
      15                 : #include <boost/capy/buffers.hpp>
      16                 : #include <boost/capy/buffers/buffer_copy.hpp>
      17                 : #include <boost/capy/buffers/make_buffer.hpp>
      18                 : #include <boost/capy/cond.hpp>
      19                 : #include <coroutine>
      20                 : #include <boost/capy/ex/io_env.hpp>
      21                 : #include <boost/capy/io_result.hpp>
      22                 : #include <boost/capy/test/fuse.hpp>
      23                 : 
      24                 : #include <string>
      25                 : #include <string_view>
      26                 : 
      27                 : namespace boost {
      28                 : namespace capy {
      29                 : namespace test {
      30                 : 
      31                 : /** Buffers data supplied via `provide`, then hands it out through `read_some`.
      32                 : 
      33                 :     Use this to verify code that performs reads without needing
      34                 :     real I/O. Call @ref provide to supply data, then @ref read_some
      35                 :     to consume it. The associated @ref fuse enables error injection
      36                 :     at controlled points. An optional `max_read_size` constructor
      37                 :     parameter limits bytes per read to simulate chunked delivery.
      38                 : 
      39                 :     This class satisfies the @ref ReadStream concept.
      40                 : 
      41                 :     @par Thread Safety
      42                 :     Not thread-safe.
      43                 : 
      44                 :     @par Example
      45                 :     @par !example example
      46                 : 
      47                 : 
      48                 :     @see fuse, ReadStream
      49                 : */
      50                 : class read_stream
      51                 : {
      52                 :     fuse f_;
      53                 :     std::string data_;
      54                 :     std::size_t pos_ = 0;
      55                 :     std::size_t max_read_size_;
      56                 : 
      57                 : public:
      58                 :     /** Construct a read stream.
      59                 : 
      60                 :         @param f The fuse used to inject errors during reads.
      61                 : 
      62                 :         @param max_read_size Maximum bytes returned per read.
      63                 :         Use to simulate chunked network delivery.
      64                 :     */
      65 HIT         305 :     explicit read_stream(
      66                 :         fuse f = {},
      67                 :         std::size_t max_read_size = std::size_t(-1)) noexcept
      68             305 :         : f_(std::move(f))
      69             305 :         , max_read_size_(max_read_size)
      70                 :     {
      71             305 :     }
      72                 : 
      73                 :     /** Append data to be returned by subsequent reads.
      74                 : 
      75                 :         Multiple calls accumulate data that @ref read_some returns.
      76                 : 
      77                 :         @param sv The data to append.
      78                 :     */
      79                 :     void
      80             307 :     provide(std::string_view sv)
      81                 :     {
      82             307 :         data_.append(sv);
      83             307 :     }
      84                 : 
      85                 :     /// Clear all data and reset the read position.
      86                 :     void
      87               6 :     clear() noexcept
      88                 :     {
      89               6 :         data_.clear();
      90               6 :         pos_ = 0;
      91               6 :     }
      92                 : 
      93                 :     /** Return the number of bytes available for reading.
      94                 : 
      95                 :         @return The number of provided bytes not yet consumed.
      96                 :     */
      97                 :     std::size_t
      98              24 :     available() const noexcept
      99                 :     {
     100              24 :         return data_.size() - pos_;
     101                 :     }
     102                 : 
     103                 :     /** Asynchronously read data from the stream.
     104                 : 
     105                 :         Transfers up to `buffer_size( buffers )` bytes from the internal
     106                 :         buffer to the provided mutable buffer sequence. If no data remains,
     107                 :         returns `error::eof`. Before every read, the attached @ref fuse is
     108                 :         consulted to possibly inject an error for testing fault scenarios.
     109                 :         The returned `std::size_t` is the number of bytes transferred.
     110                 : 
     111                 :         @par Effects
     112                 :         On success, advances the internal read position by the number of
     113                 :         bytes copied. If an error is injected by the fuse, the read position
     114                 :         remains unchanged.
     115                 : 
     116                 :         @par Exception Safety
     117                 :         Injected I/O conditions are reported via the `error_code`
     118                 :         component of the result. Throws `std::system_error` only when
     119                 :         the attached @ref fuse is in exception mode and reaches its
     120                 :         failure point; no-throw otherwise.
     121                 : 
     122                 :         @par Cancellation
     123                 :         If the environment's stop token is requested, the read
     124                 :         completes immediately with `error::canceled` and transfers no
     125                 :         data. This lets code under test exercise its cancellation paths.
     126                 :         An empty buffer sequence is a no-op that completes successfully
     127                 :         regardless of the stop token.
     128                 : 
     129                 :         @param buffers The mutable buffer sequence to receive data.
     130                 : 
     131                 :         @return An awaitable that await-returns `(error_code,std::size_t)`.
     132                 : 
     133                 :         @throws std::system_error When the attached @ref fuse is in
     134                 :         exception mode and reaches its failure point.
     135                 : 
     136                 :         @see fuse
     137                 :     */
     138                 :     template<MutableBufferSequence MB>
     139                 :     auto
     140             430 :     read_some(MB buffers)
     141                 :     {
     142                 :         struct awaitable
     143                 :         {
     144                 :             read_stream* self_;
     145                 :             MB buffers_;
     146                 :             bool canceled_ = false;
     147                 : 
     148             430 :             bool await_ready() const noexcept { return false; }
     149                 : 
     150                 :             // The operation completes synchronously, but await_suspend
     151                 :             // is the only place io_env is delivered (the promise's
     152                 :             // transform_awaiter forwards it here). Returning false means
     153                 :             // the coroutine does not actually suspend — it resumes
     154                 :             // immediately — so the read still completes synchronously
     155                 :             // while having observed the stop token. See io_env, IoAwaitable.
     156                 :             bool
     157             430 :             await_suspend(
     158                 :                 std::coroutine_handle<>,
     159                 :                 io_env const* env) noexcept
     160                 :             {
     161             430 :                 canceled_ = env->stop_token.stop_requested();
     162             430 :                 return false;
     163                 :             }
     164                 : 
     165                 :             [[nodiscard]] io_result<std::size_t>
     166             430 :             await_resume()
     167                 :             {
     168                 :                 // Empty buffer is a no-op regardless of
     169                 :                 // stream state, stop token, or fuse.
     170             430 :                 if(buffer_empty(buffers_))
     171               7 :                     return {std::error_code(), 0};
     172                 : 
     173             423 :                 if(canceled_)
     174               2 :                     return {error::canceled, 0};
     175                 : 
     176             421 :                 auto ec = self_->f_.maybe_fail();
     177             330 :                 if(ec)
     178              91 :                     return {ec, 0};
     179                 : 
     180             239 :                 if(self_->pos_ >= self_->data_.size())
     181              37 :                     return {error::eof, 0};
     182                 : 
     183             202 :                 std::size_t avail = self_->data_.size() - self_->pos_;
     184             202 :                 if(avail > self_->max_read_size_)
     185              24 :                     avail = self_->max_read_size_;
     186             202 :                 auto src = make_buffer(self_->data_.data() + self_->pos_, avail);
     187             202 :                 std::size_t const n = buffer_copy(buffers_, src);
     188             202 :                 self_->pos_ += n;
     189             202 :                 return {std::error_code(), n};
     190                 :             }
     191                 :         };
     192             430 :         return awaitable{this, buffers};
     193                 :     }
     194                 : };
     195                 : 
     196                 : } // test
     197                 : } // capy
     198                 : } // boost
     199                 : 
     200                 : #endif
        

Generated by: LCOV version 2.3