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-04 19:49:07 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              :             // This method is required to satisfy Capy's IoAwaitable concept,
     169              :             // but is never called because await_ready() returns true.
     170              :             //
     171              :             // Capy uses a two-layer awaitable system: the promise's
     172              :             // await_transform wraps awaitables in a transform_awaiter whose
     173              :             // standard await_suspend(coroutine_handle) calls this custom
     174              :             // 3-argument overload, passing the executor and stop_token from
     175              :             // the coroutine's context. For synchronous test awaitables like
     176              :             // this one, the coroutine never suspends, so this is not invoked.
     177              :             // The signature exists to allow the same awaitable type to work
     178              :             // with both synchronous (test) and asynchronous (real I/O) code.
     179            0 :             void await_suspend(
     180              :                 coro,
     181              :                 executor_ref,
     182              :                 std::stop_token) const noexcept
     183              :             {
     184            0 :             }
     185              : 
     186              :             io_result<>
     187          468 :             await_resume()
     188              :             {
     189          468 :                 auto ec = self_->f_->maybe_fail();
     190          428 :                 if(ec)
     191           40 :                     return {ec};
     192              : 
     193          388 :                 std::size_t to_commit = (std::min)(n_, self_->prepare_size_);
     194          388 :                 self_->data_.append(self_->prepare_buf_.data(), to_commit);
     195          388 :                 self_->prepare_size_ = 0;
     196              : 
     197          388 :                 return {};
     198              :             }
     199              :         };
     200          468 :         return awaitable{this, n};
     201              :     }
     202              : 
     203              :     /** Commit bytes written with optional end-of-stream.
     204              : 
     205              :         Transfers `n` bytes from the prepared buffer to the internal
     206              :         data buffer. If `eof` is true, marks the sink as finalized.
     207              : 
     208              :         @param n The number of bytes to commit.
     209              :         @param eof If true, signals end-of-stream after committing.
     210              : 
     211              :         @return An awaitable yielding `(error_code)`.
     212              : 
     213              :         @see fuse
     214              :     */
     215              :     auto
     216           48 :     commit(std::size_t n, bool eof)
     217              :     {
     218              :         struct awaitable
     219              :         {
     220              :             buffer_sink* self_;
     221              :             std::size_t n_;
     222              :             bool eof_;
     223              : 
     224           48 :             bool await_ready() const noexcept { return true; }
     225              : 
     226              :             // This method is required to satisfy Capy's IoAwaitable concept,
     227              :             // but is never called because await_ready() returns true.
     228              :             // See the comment on commit(std::size_t) for a detailed explanation.
     229            0 :             void await_suspend(
     230              :                 coro,
     231              :                 executor_ref,
     232              :                 std::stop_token) const noexcept
     233              :             {
     234            0 :             }
     235              : 
     236              :             io_result<>
     237           48 :             await_resume()
     238              :             {
     239           48 :                 auto ec = self_->f_->maybe_fail();
     240           37 :                 if(ec)
     241           11 :                     return {ec};
     242              : 
     243           26 :                 std::size_t to_commit = (std::min)(n_, self_->prepare_size_);
     244           26 :                 self_->data_.append(self_->prepare_buf_.data(), to_commit);
     245           26 :                 self_->prepare_size_ = 0;
     246              : 
     247           26 :                 if(eof_)
     248            4 :                     self_->eof_called_ = true;
     249              : 
     250           26 :                 return {};
     251              :             }
     252              :         };
     253           48 :         return awaitable{this, n, eof};
     254              :     }
     255              : 
     256              :     /** Signal end-of-stream.
     257              : 
     258              :         Marks the sink as finalized, indicating no more data will be
     259              :         written. Before signaling, the attached @ref fuse is consulted
     260              :         to possibly inject an error for testing fault scenarios.
     261              : 
     262              :         @return An awaitable yielding `(error_code)`.
     263              : 
     264              :         @see fuse
     265              :     */
     266              :     auto
     267          110 :     commit_eof()
     268              :     {
     269              :         struct awaitable
     270              :         {
     271              :             buffer_sink* self_;
     272              : 
     273          110 :             bool await_ready() const noexcept { return true; }
     274              : 
     275              :             // This method is required to satisfy Capy's IoAwaitable concept,
     276              :             // but is never called because await_ready() returns true.
     277              :             // See the comment on commit(std::size_t) for a detailed explanation.
     278            0 :             void await_suspend(
     279              :                 coro,
     280              :                 executor_ref,
     281              :                 std::stop_token) const noexcept
     282              :             {
     283            0 :             }
     284              : 
     285              :             io_result<>
     286          110 :             await_resume()
     287              :             {
     288          110 :                 auto ec = self_->f_->maybe_fail();
     289           82 :                 if(ec)
     290           28 :                     return {ec};
     291              : 
     292           54 :                 self_->eof_called_ = true;
     293           54 :                 return {};
     294              :             }
     295              :         };
     296          110 :         return awaitable{this};
     297              :     }
     298              : };
     299              : 
     300              : } // test
     301              : } // capy
     302              : } // boost
     303              : 
     304              : #endif
        

Generated by: LCOV version 2.3