LCOV - code coverage report
Current view: top level - capy/test - buffer_sink.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 88.6 % 44 39
Test Date: 2026-02-12 14:50:59 Functions: 84.6 % 13 11

            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_SINK_HPP
      11              : #define BOOST_CAPY_TEST_BUFFER_SINK_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 <coroutine>
      17              : #include <boost/capy/ex/io_env.hpp>
      18              : #include <boost/capy/io_result.hpp>
      19              : #include <boost/capy/test/fuse.hpp>
      20              : 
      21              : #include <algorithm>
      22              : #include <span>
      23              : #include <string>
      24              : #include <string_view>
      25              : 
      26              : namespace boost {
      27              : namespace capy {
      28              : namespace test {
      29              : 
      30              : /** A mock buffer sink for testing callee-owns-buffers write operations.
      31              : 
      32              :     Use this to verify code that writes data using the callee-owns-buffers
      33              :     pattern without needing real I/O. Call @ref prepare to get writable
      34              :     buffers, write into them, then call @ref commit to finalize. The
      35              :     associated @ref fuse enables error injection at controlled points.
      36              : 
      37              :     This class satisfies the @ref BufferSink concept by providing
      38              :     internal storage that callers write into directly.
      39              : 
      40              :     @par Thread Safety
      41              :     Not thread-safe.
      42              : 
      43              :     @par Example
      44              :     @code
      45              :     fuse f;
      46              :     buffer_sink bs( f );
      47              : 
      48              :     auto r = f.armed( [&]( fuse& ) -> task<void> {
      49              :         mutable_buffer arr[16];
      50              :         std::size_t count = bs.prepare( arr, 16 );
      51              :         if( count == 0 )
      52              :             co_return;
      53              : 
      54              :         // Write data into arr[0]
      55              :         std::memcpy( arr[0].data(), "Hello", 5 );
      56              : 
      57              :         auto [ec] = co_await bs.commit( 5 );
      58              :         if( ec )
      59              :             co_return;
      60              : 
      61              :         auto [ec2] = co_await bs.commit_eof();
      62              :         // bs.data() returns "Hello"
      63              :     } );
      64              :     @endcode
      65              : 
      66              :     @see fuse, BufferSink
      67              : */
      68              : class buffer_sink
      69              : {
      70              :     fuse f_;
      71              :     std::string data_;
      72              :     std::string prepare_buf_;
      73              :     std::size_t prepare_size_ = 0;
      74              :     std::size_t max_prepare_size_;
      75              :     bool eof_called_ = false;
      76              : 
      77              : public:
      78              :     /** Construct a buffer sink.
      79              : 
      80              :         @param f The fuse used to inject errors during commits.
      81              : 
      82              :         @param max_prepare_size Maximum bytes available per prepare.
      83              :         Use to simulate limited buffer space.
      84              :     */
      85          464 :     explicit buffer_sink(
      86              :         fuse f = {},
      87              :         std::size_t max_prepare_size = 4096) noexcept
      88          464 :         : f_(std::move(f))
      89          464 :         , max_prepare_size_(max_prepare_size)
      90              :     {
      91          464 :         prepare_buf_.resize(max_prepare_size_);
      92          464 :     }
      93              : 
      94              :     /// Return the written data as a string view.
      95              :     std::string_view
      96           64 :     data() const noexcept
      97              :     {
      98           64 :         return data_;
      99              :     }
     100              : 
     101              :     /// Return the number of bytes written.
     102              :     std::size_t
     103            4 :     size() const noexcept
     104              :     {
     105            4 :         return data_.size();
     106              :     }
     107              : 
     108              :     /// Return whether commit_eof has been called.
     109              :     bool
     110           62 :     eof_called() const noexcept
     111              :     {
     112           62 :         return eof_called_;
     113              :     }
     114              : 
     115              :     /// Clear all data and reset state.
     116              :     void
     117              :     clear() noexcept
     118              :     {
     119              :         data_.clear();
     120              :         prepare_size_ = 0;
     121              :         eof_called_ = false;
     122              :     }
     123              : 
     124              :     /** Prepare writable buffers.
     125              : 
     126              :         Fills the provided span with mutable buffer descriptors pointing
     127              :         to internal storage. The caller writes data into these buffers,
     128              :         then calls @ref commit to finalize.
     129              : 
     130              :         @param dest Span of mutable_buffer to fill.
     131              : 
     132              :         @return A span of filled buffers (empty or 1 buffer in this implementation).
     133              :     */
     134              :     std::span<mutable_buffer>
     135          708 :     prepare(std::span<mutable_buffer> dest)
     136              :     {
     137          708 :         if(dest.empty())
     138            0 :             return {};
     139              : 
     140          708 :         prepare_size_ = max_prepare_size_;
     141          708 :         dest[0] = make_buffer(prepare_buf_.data(), prepare_size_);
     142          708 :         return dest.first(1);
     143              :     }
     144              : 
     145              :     /** Commit bytes written to the prepared buffers.
     146              : 
     147              :         Transfers `n` bytes from the prepared buffer to the internal
     148              :         data buffer. Before committing, the attached @ref fuse is
     149              :         consulted to possibly inject an error for testing fault scenarios.
     150              : 
     151              :         @param n The number of bytes to commit.
     152              : 
     153              :         @return An awaitable yielding `(error_code)`.
     154              : 
     155              :         @see fuse
     156              :     */
     157              :     auto
     158          476 :     commit(std::size_t n)
     159              :     {
     160              :         struct awaitable
     161              :         {
     162              :             buffer_sink* self_;
     163              :             std::size_t n_;
     164              : 
     165          476 :             bool await_ready() const noexcept { return true; }
     166              : 
     167              :             // This method is required to satisfy Capy's IoAwaitable concept,
     168              :             // but is never called because await_ready() returns true.
     169              :             //
     170              :             // Capy uses a two-layer awaitable system: the promise's
     171              :             // await_transform wraps awaitables in a transform_awaiter whose
     172              :             // standard await_suspend(coroutine_handle) calls this custom
     173              :             // 2-argument overload, passing the io_env from the coroutine's
     174              :             // context. For synchronous test awaitables like this one, the
     175              :             // coroutine never suspends, so this is not invoked. The signature
     176              :             // exists to allow the same awaitable type to work with both
     177              :             // synchronous (test) and asynchronous (real I/O) code.
     178            0 :             void await_suspend(
     179              :                 std::coroutine_handle<>,
     180              :                 io_env const*) const noexcept
     181              :             {
     182            0 :             }
     183              : 
     184              :             io_result<>
     185          476 :             await_resume()
     186              :             {
     187          476 :                 auto ec = self_->f_.maybe_fail();
     188          416 :                 if(ec)
     189           60 :                     return {ec};
     190              : 
     191          356 :                 std::size_t to_commit = (std::min)(n_, self_->prepare_size_);
     192          356 :                 self_->data_.append(self_->prepare_buf_.data(), to_commit);
     193          356 :                 self_->prepare_size_ = 0;
     194              : 
     195          356 :                 return {};
     196              :             }
     197              :         };
     198          476 :         return awaitable{this, n};
     199              :     }
     200              : 
     201              :     /** Commit final bytes and signal end-of-stream.
     202              : 
     203              :         Transfers `n` bytes from the prepared buffer to the internal
     204              :         data buffer and marks the sink as finalized. Before committing,
     205              :         the attached @ref fuse is consulted to possibly inject an error
     206              :         for testing fault scenarios.
     207              : 
     208              :         @param n The number of bytes to commit.
     209              : 
     210              :         @return An awaitable yielding `(error_code)`.
     211              : 
     212              :         @see fuse
     213              :     */
     214              :     auto
     215          162 :     commit_eof(std::size_t n)
     216              :     {
     217              :         struct awaitable
     218              :         {
     219              :             buffer_sink* self_;
     220              :             std::size_t n_;
     221              : 
     222          162 :             bool await_ready() const noexcept { return true; }
     223              : 
     224              :             // This method is required to satisfy Capy's IoAwaitable concept,
     225              :             // but is never called because await_ready() returns true.
     226              :             // See the comment on commit(std::size_t) for a detailed explanation.
     227            0 :             void await_suspend(
     228              :                 std::coroutine_handle<>,
     229              :                 io_env const*) const noexcept
     230              :             {
     231            0 :             }
     232              : 
     233              :             io_result<>
     234          162 :             await_resume()
     235              :             {
     236          162 :                 auto ec = self_->f_.maybe_fail();
     237          118 :                 if(ec)
     238           44 :                     return {ec};
     239              : 
     240           74 :                 std::size_t to_commit = (std::min)(n_, self_->prepare_size_);
     241           74 :                 self_->data_.append(self_->prepare_buf_.data(), to_commit);
     242           74 :                 self_->prepare_size_ = 0;
     243              : 
     244           74 :                 self_->eof_called_ = true;
     245           74 :                 return {};
     246              :             }
     247              :         };
     248          162 :         return awaitable{this, n};
     249              :     }
     250              : };
     251              : 
     252              : } // test
     253              : } // capy
     254              : } // boost
     255              : 
     256              : #endif
        

Generated by: LCOV version 2.3