LCOV - code coverage report
Current view: top level - capy/test - buffer_sink.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 87.5 % 56 49
Test Date: 2026-02-02 05:00:52 Functions: 82.4 % 17 14

            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 <boost/capy/coro.hpp>
      17              : #include <boost/capy/ex/executor_ref.hpp>
      18              : #include <boost/capy/io_result.hpp>
      19              : #include <boost/capy/test/fuse.hpp>
      20              : 
      21              : #include <algorithm>
      22              : #include <span>
      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 sink for testing callee-owns-buffers write operations.
      32              : 
      33              :     Use this to verify code that writes data using the callee-owns-buffers
      34              :     pattern without needing real I/O. Call @ref prepare to get writable
      35              :     buffers, write into them, then call @ref commit to finalize. The
      36              :     associated @ref fuse enables error injection at controlled points.
      37              : 
      38              :     This class satisfies the @ref BufferSink concept by providing
      39              :     internal storage that callers write into directly.
      40              : 
      41              :     @par Thread Safety
      42              :     Not thread-safe.
      43              : 
      44              :     @par Example
      45              :     @code
      46              :     fuse f;
      47              :     buffer_sink bs( f );
      48              : 
      49              :     auto r = f.armed( [&]( fuse& ) -> task<void> {
      50              :         mutable_buffer arr[16];
      51              :         std::size_t count = bs.prepare( arr, 16 );
      52              :         if( count == 0 )
      53              :             co_return;
      54              : 
      55              :         // Write data into arr[0]
      56              :         std::memcpy( arr[0].data(), "Hello", 5 );
      57              : 
      58              :         auto [ec] = co_await bs.commit( 5 );
      59              :         if( ec )
      60              :             co_return;
      61              : 
      62              :         auto [ec2] = co_await bs.commit_eof();
      63              :         // bs.data() returns "Hello"
      64              :     } );
      65              :     @endcode
      66              : 
      67              :     @see fuse, BufferSink
      68              : */
      69              : class buffer_sink
      70              : {
      71              :     fuse* f_;
      72              :     std::string data_;
      73              :     std::string prepare_buf_;
      74              :     std::size_t prepare_size_ = 0;
      75              :     std::size_t max_prepare_size_;
      76              :     bool eof_called_ = false;
      77              : 
      78              : public:
      79              :     /** Construct a buffer sink.
      80              : 
      81              :         @param f The fuse used to inject errors during commits.
      82              : 
      83              :         @param max_prepare_size Maximum bytes available per prepare.
      84              :         Use to simulate limited buffer space.
      85              :     */
      86          404 :     explicit buffer_sink(
      87              :         fuse& f,
      88              :         std::size_t max_prepare_size = 4096) noexcept
      89          404 :         : f_(&f)
      90          404 :         , max_prepare_size_(max_prepare_size)
      91              :     {
      92          404 :         prepare_buf_.resize(max_prepare_size_);
      93          404 :     }
      94              : 
      95              :     /// Return the written data as a string view.
      96              :     std::string_view
      97           48 :     data() const noexcept
      98              :     {
      99           48 :         return data_;
     100              :     }
     101              : 
     102              :     /// Return the number of bytes written.
     103              :     std::size_t
     104            4 :     size() const noexcept
     105              :     {
     106            4 :         return data_.size();
     107              :     }
     108              : 
     109              :     /// Return whether commit_eof has been called.
     110              :     bool
     111           50 :     eof_called() const noexcept
     112              :     {
     113           50 :         return eof_called_;
     114              :     }
     115              : 
     116              :     /// Clear all data and reset state.
     117              :     void
     118              :     clear() noexcept
     119              :     {
     120              :         data_.clear();
     121              :         prepare_size_ = 0;
     122              :         eof_called_ = false;
     123              :     }
     124              : 
     125              :     /** Prepare writable buffers.
     126              : 
     127              :         Fills the provided span with mutable buffer descriptors pointing
     128              :         to internal storage. The caller writes data into these buffers,
     129              :         then calls @ref commit to finalize.
     130              : 
     131              :         @param dest Span of mutable_buffer to fill.
     132              : 
     133              :         @return A span of filled buffers (empty or 1 buffer in this implementation).
     134              :     */
     135              :     std::span<mutable_buffer>
     136          800 :     prepare(std::span<mutable_buffer> dest)
     137              :     {
     138          800 :         if(dest.empty())
     139            0 :             return {};
     140              : 
     141          800 :         prepare_size_ = max_prepare_size_;
     142          800 :         dest[0] = make_buffer(prepare_buf_.data(), prepare_size_);
     143          800 :         return dest.first(1);
     144              :     }
     145              : 
     146              :     /** Commit bytes written to the prepared buffers.
     147              : 
     148              :         Transfers `n` bytes from the prepared buffer to the internal
     149              :         data buffer. Before committing, the attached @ref fuse is
     150              :         consulted to possibly inject an error for testing fault scenarios.
     151              : 
     152              :         @param n The number of bytes to commit.
     153              : 
     154              :         @return An awaitable yielding `(error_code)`.
     155              : 
     156              :         @see fuse
     157              :     */
     158              :     auto
     159          468 :     commit(std::size_t n)
     160              :     {
     161              :         struct awaitable
     162              :         {
     163              :             buffer_sink* self_;
     164              :             std::size_t n_;
     165              : 
     166          468 :             bool await_ready() const noexcept { return true; }
     167              : 
     168            0 :             void await_suspend(
     169              :                 coro,
     170              :                 executor_ref,
     171              :                 std::stop_token) const noexcept
     172              :             {
     173            0 :             }
     174              : 
     175              :             io_result<>
     176          468 :             await_resume()
     177              :             {
     178          468 :                 auto ec = self_->f_->maybe_fail();
     179          428 :                 if(ec)
     180           40 :                     return {ec};
     181              : 
     182          388 :                 std::size_t to_commit = (std::min)(n_, self_->prepare_size_);
     183          388 :                 self_->data_.append(self_->prepare_buf_.data(), to_commit);
     184          388 :                 self_->prepare_size_ = 0;
     185              : 
     186          388 :                 return {};
     187              :             }
     188              :         };
     189          468 :         return awaitable{this, n};
     190              :     }
     191              : 
     192              :     /** Commit bytes written with optional end-of-stream.
     193              : 
     194              :         Transfers `n` bytes from the prepared buffer to the internal
     195              :         data buffer. If `eof` is true, marks the sink as finalized.
     196              : 
     197              :         @param n The number of bytes to commit.
     198              :         @param eof If true, signals end-of-stream after committing.
     199              : 
     200              :         @return An awaitable yielding `(error_code)`.
     201              : 
     202              :         @see fuse
     203              :     */
     204              :     auto
     205           48 :     commit(std::size_t n, bool eof)
     206              :     {
     207              :         struct awaitable
     208              :         {
     209              :             buffer_sink* self_;
     210              :             std::size_t n_;
     211              :             bool eof_;
     212              : 
     213           48 :             bool await_ready() const noexcept { return true; }
     214              : 
     215            0 :             void await_suspend(
     216              :                 coro,
     217              :                 executor_ref,
     218              :                 std::stop_token) const noexcept
     219              :             {
     220            0 :             }
     221              : 
     222              :             io_result<>
     223           48 :             await_resume()
     224              :             {
     225           48 :                 auto ec = self_->f_->maybe_fail();
     226           37 :                 if(ec)
     227           11 :                     return {ec};
     228              : 
     229           26 :                 std::size_t to_commit = (std::min)(n_, self_->prepare_size_);
     230           26 :                 self_->data_.append(self_->prepare_buf_.data(), to_commit);
     231           26 :                 self_->prepare_size_ = 0;
     232              : 
     233           26 :                 if(eof_)
     234            4 :                     self_->eof_called_ = true;
     235              : 
     236           26 :                 return {};
     237              :             }
     238              :         };
     239           48 :         return awaitable{this, n, eof};
     240              :     }
     241              : 
     242              :     /** Signal end-of-stream.
     243              : 
     244              :         Marks the sink as finalized, indicating no more data will be
     245              :         written. Before signaling, the attached @ref fuse is consulted
     246              :         to possibly inject an error for testing fault scenarios.
     247              : 
     248              :         @return An awaitable yielding `(error_code)`.
     249              : 
     250              :         @see fuse
     251              :     */
     252              :     auto
     253          110 :     commit_eof()
     254              :     {
     255              :         struct awaitable
     256              :         {
     257              :             buffer_sink* self_;
     258              : 
     259          110 :             bool await_ready() const noexcept { return true; }
     260              : 
     261            0 :             void await_suspend(
     262              :                 coro,
     263              :                 executor_ref,
     264              :                 std::stop_token) const noexcept
     265              :             {
     266            0 :             }
     267              : 
     268              :             io_result<>
     269          110 :             await_resume()
     270              :             {
     271          110 :                 auto ec = self_->f_->maybe_fail();
     272           82 :                 if(ec)
     273           28 :                     return {ec};
     274              : 
     275           54 :                 self_->eof_called_ = true;
     276           54 :                 return {};
     277              :             }
     278              :         };
     279          110 :         return awaitable{this};
     280              :     }
     281              : };
     282              : 
     283              : } // test
     284              : } // capy
     285              : } // boost
     286              : 
     287              : #endif
        

Generated by: LCOV version 2.3