Mock Buffer Sources and Sinks
Concept-conforming test doubles for the buffer concepts in
Buffer Sources and Sinks. These
mocks let you test code that consumes via a BufferSource or produces via
a BufferSink without wiring up a real dynamic buffer.
buffer_source
buffer_source implements the BufferSource concept. Test code stages
bytes via provide(), and the system under test pulls them through the
pull()/consume() interface that BufferSource requires. Pulled buffers
point directly into the source’s internal storage, so no copy occurs. The
attached fuse injects errors at every pull() call, exercising the
caller’s error-handling paths. Because fuse copies share state (see
Shared State Across Copies),
constructing buffer_source bs(f) by value still ties bs to the same
fail-point machinery as f.
#include <boost/capy/test/buffer_source.hpp>
#include <boost/capy/test/fuse.hpp>
#include <boost/capy/test/buffer_to_string.hpp>
#include <boost/capy/buffers.hpp>
#include <boost/capy/task.hpp>
using namespace boost::capy;
using namespace boost::capy::test;
void test_buffer_source()
{
fuse f;
buffer_source bs(f);
bs.provide("Hello, ");
bs.provide("World!");
auto r = f.armed([&](fuse&) -> task<void> {
const_buffer arr[16];
auto [ec, bufs] = co_await bs.pull(arr);
if(ec)
co_return;
BOOST_TEST(buffer_to_string(bufs) == "Hello, World!");
bs.consume(buffer_size(bufs));
});
BOOST_TEST(r.success);
}
Staging Data
Call provide() one or more times before the system under test runs.
Each call appends bytes to the internal buffer; the next pull() returns
a span covering all accumulated unconsumed data, up to max_pull_size if
a limit was set.
buffer_source bs(f);
bs.provide("part one ");
bs.provide("part two"); // total: "part one part two"
Consume Loop
pull() returns the same data on repeated calls until consume() advances
the read position. A typical consumer loops until pull() returns
cond::eof, consuming the returned bytes each time:
const_buffer arr[16];
for(;;)
{
auto [ec, bufs] = co_await bs.pull(arr);
if(ec == cond::eof)
break;
if(ec)
co_return; // fuse injected error, or real failure
// process bufs ...
bs.consume(buffer_size(bufs));
}
Chunked Delivery
The second constructor parameter caps the bytes returned per pull(),
simulating a source that delivers data in small pieces:
buffer_source bs(f, 5); // at most 5 bytes per pull
bs.provide("hello world");
// first pull returns "hello"; second returns " worl"; etc.
| Member | Description |
|---|---|
|
Construct with an optional shared |
|
Append bytes to the internal buffer for subsequent pulls. Multiple calls accumulate data. |
|
Fills |
|
Advance the read position by |
|
Return the number of bytes not yet consumed. |
|
Clear all data and reset the read position. |
buffer_sink
buffer_sink implements the BufferSink concept. The system under test
follows the callee-owns-buffers pattern: it calls prepare() to get
writable buffer space from the sink, writes directly into those buffers,
then calls commit() or commit_eof() to finalize the bytes. The test
then inspects what was captured via data() and checks whether the
end-of-stream was signaled via eof_called(). The attached fuse
injects errors at every async step. Because fuse copies share state (see
Shared State Across Copies),
constructing buffer_sink bs(f) by value still ties bs to the same
fail-point machinery as f.
#include <boost/capy/test/buffer_sink.hpp>
#include <boost/capy/test/fuse.hpp>
#include <boost/capy/task.hpp>
#include <cstring>
using namespace boost::capy;
using namespace boost::capy::test;
void test_buffer_sink()
{
fuse f;
auto r = f.armed([&](fuse&) -> task<void> {
buffer_sink bs(f);
mutable_buffer arr[16];
auto bufs = bs.prepare(arr);
std::memcpy(bufs[0].data(), "Hello", 5);
auto [ec] = co_await bs.commit(5);
if(ec)
co_return;
auto [ec2] = co_await bs.commit_eof(0);
if(ec2)
co_return;
BOOST_TEST(bs.data() == "Hello");
BOOST_TEST(bs.eof_called());
});
BOOST_TEST(r.success);
}
Reading What Was Written
After the coroutine completes, data() returns a string_view of all
committed bytes. size() gives the byte count. eof_called() returns
true if commit_eof() succeeded during the run.
BOOST_TEST(bs.data() == "expected output");
BOOST_TEST(bs.size() == 15u);
BOOST_TEST(bs.eof_called());
Call these accessors inside the f.armed() lambda after the system
under test completes successfully. They are the primary mechanism for
asserting what the system under test produced.
The prepare/commit Protocol
prepare() is synchronous. It fills the provided span with one writable
buffer descriptor pointing into the sink’s internal storage. The caller
writes data into those buffers, then calls commit(n) to finalize n
bytes, or commit_eof(n) to finalize n bytes and signal end-of-stream
in a single step. Passing n = 0 to commit_eof signals EOF without
writing additional bytes.
Limited Buffer Space
The second constructor parameter caps the bytes available per prepare(),
simulating a sink with constrained internal space:
buffer_sink bs(f, 8); // prepare returns at most 8 bytes at a time
| Member | Description |
|---|---|
|
Construct with an optional shared |
|
Synchronously fills |
|
Finalize |
|
Finalize |
|
Return all bytes committed so far. |
|
Return the number of bytes committed. |
|
Return |
|
Clear all committed data and reset |
Putting It Together
The following snippet tests a copy algorithm that pulls from a
BufferSource and writes into a BufferSink. The fuse.armed() loop
exercises every error site in both the pull and commit paths.
#include <boost/capy/buffers.hpp>
#include <boost/capy/concept/buffer_sink.hpp>
#include <boost/capy/concept/buffer_source.hpp>
#include <boost/capy/cond.hpp>
#include <boost/capy/task.hpp>
#include <boost/capy/test/buffer_sink.hpp>
#include <boost/capy/test/buffer_source.hpp>
#include <boost/capy/test/fuse.hpp>
#include <cstring>
using namespace boost::capy;
using namespace boost::capy::test;
// Function under test: copy all bytes from source into sink
template<BufferSource Source, BufferSink Sink>
task<std::error_code>
copy_all(Source& source, Sink& sink)
{
const_buffer src_arr[16];
mutable_buffer dst_arr[16];
for(;;)
{
auto [ec1, src_bufs] = co_await source.pull(src_arr);
if(ec1 == cond::eof)
{
auto [eof_ec] = co_await sink.commit_eof(0);
co_return eof_ec;
}
if(ec1)
co_return ec1;
auto dst_bufs = sink.prepare(dst_arr);
std::size_t n = buffer_copy(dst_bufs, src_bufs);
auto [ec2] = co_await sink.commit(n);
if(ec2)
co_return ec2;
source.consume(n);
}
}
void test_copy_all()
{
fuse f;
auto r = f.armed([&](fuse&) -> task<void> {
buffer_source src(f);
buffer_sink dst(f);
src.provide("ping");
auto ec = co_await copy_all(src, dst);
if(ec)
co_return; // fuse injected an error; exit gracefully
BOOST_TEST(dst.data() == "ping");
BOOST_TEST(dst.eof_called());
});
BOOST_TEST(r.success);
}
Reference
| Header | Contents |
|---|---|
|
Mock BufferSource for callee-owns-buffers pull tests. |
|
Mock BufferSink for callee-owns-buffers write tests. |
Continue to Buffer Inspection.