LCOV - code coverage report
Current view: top level - capy/test - buffer_sink.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 91.8 % 49 45 4
Test Date: 2026-02-17 18:14:47 Functions: 85.7 % 14 12 2

           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_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 HIT         528 :     explicit buffer_sink(
      86                 :         fuse f = {},
      87                 :         std::size_t max_prepare_size = 4096) noexcept
      88             528 :         : f_(std::move(f))
      89             528 :         , max_prepare_size_(max_prepare_size)
      90                 :     {
      91             528 :         prepare_buf_.resize(max_prepare_size_);
      92             528 :     }
      93                 : 
      94                 :     /// Return the written data as a string view.
      95                 :     std::string_view
      96              82 :     data() const noexcept
      97                 :     {
      98              82 :         return data_;
      99                 :     }
     100                 : 
     101                 :     /// Return the number of bytes written.
     102                 :     std::size_t
     103              12 :     size() const noexcept
     104                 :     {
     105              12 :         return data_.size();
     106                 :     }
     107                 : 
     108                 :     /// Return whether commit_eof has been called.
     109                 :     bool
     110              78 :     eof_called() const noexcept
     111                 :     {
     112              78 :         return eof_called_;
     113                 :     }
     114                 : 
     115                 :     /// Clear all data and reset state.
     116                 :     void
     117               2 :     clear() noexcept
     118                 :     {
     119               2 :         data_.clear();
     120               2 :         prepare_size_ = 0;
     121               2 :         eof_called_ = false;
     122               2 :     }
     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             762 :     prepare(std::span<mutable_buffer> dest)
     136                 :     {
     137             762 :         if(dest.empty())
     138               2 :             return {};
     139                 : 
     140             760 :         prepare_size_ = max_prepare_size_;
     141             760 :         dest[0] = make_buffer(prepare_buf_.data(), prepare_size_);
     142             760 :         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             522 :     commit(std::size_t n)
     159                 :     {
     160                 :         struct awaitable
     161                 :         {
     162                 :             buffer_sink* self_;
     163                 :             std::size_t n_;
     164                 : 
     165             522 :             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 MIS           0 :             void await_suspend(
     179                 :                 std::coroutine_handle<>,
     180                 :                 io_env const*) const noexcept
     181                 :             {
     182               0 :             }
     183                 : 
     184                 :             io_result<>
     185 HIT         522 :             await_resume()
     186                 :             {
     187             522 :                 auto ec = self_->f_.maybe_fail();
     188             449 :                 if(ec)
     189              73 :                     return {ec};
     190                 : 
     191             376 :                 std::size_t to_commit = (std::min)(n_, self_->prepare_size_);
     192             376 :                 self_->data_.append(self_->prepare_buf_.data(), to_commit);
     193             376 :                 self_->prepare_size_ = 0;
     194                 : 
     195             376 :                 return {};
     196                 :             }
     197                 :         };
     198             522 :         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             188 :     commit_eof(std::size_t n)
     216                 :     {
     217                 :         struct awaitable
     218                 :         {
     219                 :             buffer_sink* self_;
     220                 :             std::size_t n_;
     221                 : 
     222             188 :             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 MIS           0 :             void await_suspend(
     228                 :                 std::coroutine_handle<>,
     229                 :                 io_env const*) const noexcept
     230                 :             {
     231               0 :             }
     232                 : 
     233                 :             io_result<>
     234 HIT         188 :             await_resume()
     235                 :             {
     236             188 :                 auto ec = self_->f_.maybe_fail();
     237             136 :                 if(ec)
     238              52 :                     return {ec};
     239                 : 
     240              84 :                 std::size_t to_commit = (std::min)(n_, self_->prepare_size_);
     241              84 :                 self_->data_.append(self_->prepare_buf_.data(), to_commit);
     242              84 :                 self_->prepare_size_ = 0;
     243                 : 
     244              84 :                 self_->eof_called_ = true;
     245              84 :                 return {};
     246                 :             }
     247                 :         };
     248             188 :         return awaitable{this, n};
     249                 :     }
     250                 : };
     251                 : 
     252                 : } // test
     253                 : } // capy
     254                 : } // boost
     255                 : 
     256                 : #endif
        

Generated by: LCOV version 2.3