LCOV - code coverage report
Current view: top level - capy/test - buffer_source.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 89.3 % 28 25
Test Date: 2026-02-07 18:59:16 Functions: 85.7 % 7 6

            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_BUFFER_SOURCE_HPP
      11              : #define BOOST_CAPY_TEST_BUFFER_SOURCE_HPP
      12              : 
      13              : #include <boost/capy/detail/config.hpp>
      14              : #include <boost/capy/buffers.hpp>
      15              : #include <boost/capy/buffers/make_buffer.hpp>
      16              : #include <boost/capy/coro.hpp>
      17              : #include <boost/capy/error.hpp>
      18              : #include <boost/capy/ex/executor_ref.hpp>
      19              : #include <boost/capy/io_result.hpp>
      20              : #include <boost/capy/test/fuse.hpp>
      21              : 
      22              : #include <algorithm>
      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 buffer source for testing push operations.
      32              : 
      33              :     Use this to verify code that transfers data from a buffer source to
      34              :     a sink without needing real I/O. Call @ref provide to supply data,
      35              :     then @ref pull to retrieve buffer descriptors. The associated
      36              :     @ref fuse enables error injection at controlled points.
      37              : 
      38              :     This class satisfies the @ref BufferSource concept by providing
      39              :     a pull interface that fills an array of buffer descriptors and
      40              :     a consume interface to indicate bytes used.
      41              : 
      42              :     @par Thread Safety
      43              :     Not thread-safe.
      44              : 
      45              :     @par Example
      46              :     @code
      47              :     fuse f;
      48              :     buffer_source bs( f );
      49              :     bs.provide( "Hello, " );
      50              :     bs.provide( "World!" );
      51              : 
      52              :     auto r = f.armed( [&]( fuse& ) -> task<void> {
      53              :         const_buffer arr[16];
      54              :         auto [ec, bufs] = co_await bs.pull( arr );
      55              :         if( ec )
      56              :             co_return;
      57              :         // bufs contains buffer descriptors
      58              :         std::size_t n = buffer_size( bufs );
      59              :         bs.consume( n );
      60              :     } );
      61              :     @endcode
      62              : 
      63              :     @see fuse, BufferSource
      64              : */
      65              : class buffer_source
      66              : {
      67              :     fuse f_;
      68              :     std::string data_;
      69              :     std::size_t pos_ = 0;
      70              :     std::size_t max_pull_size_;
      71              : 
      72              : public:
      73              :     /** Construct a buffer source.
      74              : 
      75              :         @param f The fuse used to inject errors during pulls.
      76              : 
      77              :         @param max_pull_size Maximum bytes returned per pull.
      78              :         Use to simulate chunked delivery.
      79              :     */
      80          302 :     explicit buffer_source(
      81              :         fuse f = {},
      82              :         std::size_t max_pull_size = std::size_t(-1)) noexcept
      83          302 :         : f_(std::move(f))
      84          302 :         , max_pull_size_(max_pull_size)
      85              :     {
      86          302 :     }
      87              : 
      88              :     /** Append data to be returned by subsequent pulls.
      89              : 
      90              :         Multiple calls accumulate data that @ref pull returns.
      91              : 
      92              :         @param sv The data to append.
      93              :     */
      94              :     void
      95          316 :     provide(std::string_view sv)
      96              :     {
      97          316 :         data_.append(sv);
      98          316 :     }
      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 pulling.
     109              :     std::size_t
     110              :     available() const noexcept
     111              :     {
     112              :         return data_.size() - pos_;
     113              :     }
     114              : 
     115              :     /** Consume bytes from the source.
     116              : 
     117              :         Advances the internal read position by the specified number
     118              :         of bytes. The next call to @ref pull returns data starting
     119              :         after the consumed bytes.
     120              : 
     121              :         @param n The number of bytes to consume. Must not exceed the
     122              :         total size of buffers returned by the previous @ref pull.
     123              :     */
     124              :     void
     125          271 :     consume(std::size_t n) noexcept
     126              :     {
     127          271 :         pos_ += n;
     128          271 :     }
     129              : 
     130              :     /** Pull buffer data from the source.
     131              : 
     132              :         Fills the provided span with buffer descriptors pointing to
     133              :         internal data starting from the current unconsumed position.
     134              :         Returns a span of filled buffers. When no data remains,
     135              :         returns an empty span to signal completion.
     136              : 
     137              :         Calling pull multiple times without intervening @ref consume
     138              :         returns the same data. Use consume to advance past processed
     139              :         bytes.
     140              : 
     141              :         @param dest Span of const_buffer to fill.
     142              : 
     143              :         @return An awaitable yielding `(error_code,std::span<const_buffer>)`.
     144              : 
     145              :         @see consume, fuse
     146              :     */
     147              :     auto
     148          552 :     pull(std::span<const_buffer> dest)
     149              :     {
     150              :         struct awaitable
     151              :         {
     152              :             buffer_source* self_;
     153              :             std::span<const_buffer> dest_;
     154              : 
     155          552 :             bool await_ready() const noexcept { return true; }
     156              : 
     157              :             // This method is required to satisfy Capy's IoAwaitable concept,
     158              :             // but is never called because await_ready() returns true.
     159              :             //
     160              :             // Capy uses a two-layer awaitable system: the promise's
     161              :             // await_transform wraps awaitables in a transform_awaiter whose
     162              :             // standard await_suspend(coroutine_handle) calls this custom
     163              :             // 3-argument overload, passing the executor and stop_token from
     164              :             // the coroutine's context. For synchronous test awaitables like
     165              :             // this one, the coroutine never suspends, so this is not invoked.
     166              :             // The signature exists to allow the same awaitable type to work
     167              :             // with both synchronous (test) and asynchronous (real I/O) code.
     168            0 :             void await_suspend(
     169              :                 coro,
     170              :                 executor_ref,
     171              :                 std::stop_token) const noexcept
     172              :             {
     173            0 :             }
     174              : 
     175              :             io_result<std::span<const_buffer>>
     176          552 :             await_resume()
     177              :             {
     178          552 :                 auto ec = self_->f_.maybe_fail();
     179          467 :                 if(ec)
     180           85 :                     return {ec, {}};
     181              : 
     182          382 :                 if(self_->pos_ >= self_->data_.size())
     183           66 :                     return {error::eof, {}};
     184              : 
     185          316 :                 std::size_t avail = self_->data_.size() - self_->pos_;
     186          316 :                 std::size_t to_return = (std::min)(avail, self_->max_pull_size_);
     187              : 
     188          316 :                 if(dest_.empty())
     189            0 :                     return {{}, {}};
     190              : 
     191              :                 // Fill a single buffer descriptor
     192          316 :                 dest_[0] = make_buffer(
     193          316 :                     self_->data_.data() + self_->pos_,
     194              :                     to_return);
     195              : 
     196          316 :                 return {{}, dest_.first(1)};
     197              :             }
     198              :         };
     199          552 :         return awaitable{this, dest};
     200              :     }
     201              : };
     202              : 
     203              : } // test
     204              : } // capy
     205              : } // boost
     206              : 
     207              : #endif
        

Generated by: LCOV version 2.3