Mock Sources and Sinks
Concept-conforming test doubles for the complete-I/O concepts in Sources and Sinks. Sources fill the buffer completely (looping internally if needed); sinks accept all bytes and report EOF.
read_source
read_source implements the ReadSource concept. Test code stages bytes
via provide(), then the system under test calls read() and receives the
entire requested length back (or an error or EOF). The attached fuse
injects errors at every read call, exercising the caller’s error-handling
paths. Because fuse copies share state (see
Shared State Across Copies),
constructing read_source rs(f) by value still ties rs to the same
fail-point machinery as f.
#include <boost/capy/test/read_source.hpp>
#include <boost/capy/test/fuse.hpp>
#include <boost/capy/buffers/make_buffer.hpp>
#include <boost/capy/task.hpp>
using namespace boost::capy;
using namespace boost::capy::test;
void test_read_source()
{
fuse f;
read_source rs(f);
rs.provide("Hello, ");
rs.provide("World!");
auto r = f.armed([&](fuse&) -> task<void> {
char buf[32];
auto [ec, n] = co_await rs.read(
mutable_buffer(buf, sizeof(buf)));
if(ec)
co_return;
BOOST_TEST(std::string_view(buf, n) == "Hello, World!");
});
BOOST_TEST(r.success);
}
Complete vs. Partial Reads
read_source exposes both read() and read_some(). The distinction
matters:
read_some() is a partial read, inherited from ReadStream. It returns
up to max_read_size bytes per call and may return fewer bytes than the
buffer can hold. Callers must loop to fill a buffer.
read() is a complete read, satisfying ReadSource. It transfers all
available data in a single operation, ignoring the max_read_size limit.
On success n equals buffer_size(buffers). If available data runs out
before the buffer is filled, read() returns cond::eof with n
set to however many bytes were transferred. Callers do not need to loop.
This is the key behavioral difference from read_stream::read_some(),
which always returns a partial result and never fills the buffer on its
own.
| Member | Description |
|---|---|
|
Construct with an optional shared |
|
Append bytes to the internal buffer for subsequent reads. Multiple calls accumulate data. |
|
Complete read. Transfers all available data in a single step, ignoring
|
|
Partial read. Returns up to |
|
Return the number of bytes remaining to be read. |
|
Clear all data and reset the read position. |
write_sink
write_sink implements the WriteSink concept. The system under test
calls write() and write_eof() while the test inspects what was written
via data() and checks whether EOF was signaled via eof_called().
Test code may also call expect() to register the data it anticipates;
any mismatch between written bytes and that prefix causes write_some()
to return error::test_failure. Because fuse copies share state (see
Shared State Across Copies),
constructing write_sink ws(f) by value still ties ws to the same
fail-point machinery as f.
#include <boost/capy/test/write_sink.hpp>
#include <boost/capy/test/fuse.hpp>
#include <boost/capy/buffers/make_buffer.hpp>
#include <boost/capy/task.hpp>
using namespace boost::capy;
using namespace boost::capy::test;
void test_write_sink()
{
fuse f;
write_sink ws(f);
auto r = f.armed([&](fuse&) -> task<void> {
auto [ec, n] = co_await ws.write(
const_buffer("Hello", 5));
if(ec)
co_return;
auto [ec2] = co_await ws.write_eof();
if(ec2)
co_return;
});
BOOST_TEST(r.success);
BOOST_TEST(ws.data() == "Hello");
BOOST_TEST(ws.eof_called());
}
EOF Signal
write_eof() is the explicit end-of-stream marker, with no analog in
write_stream. Some protocols treat connection close as the end-of-body
signal (HTTP/1.0 without Content-Length is one example), so the sink
needs a way to capture that event separately from the data transfer.
write_sink provides two forms of the signal:
-
write_eof()— signal EOF without data. -
write_eof(buffers)— atomically write the last chunk and signal EOF in a single awaitable. This form lets protocol code optimize the final send so data and the termination marker travel together.
After either form succeeds, eof_called() returns true. The fuse is
consulted before the operation, so both forms participate in error
injection.
| Member | Description |
|---|---|
|
Construct with an optional shared |
|
Complete write. Transfers all bytes from |
|
Partial write. Appends up to |
|
Atomically write remaining bytes and signal end-of-stream. Sets
|
|
Signal end-of-stream without writing data. Sets |
|
Return bytes written but not yet matched by |
|
Return the number of bytes written. |
|
Return |
|
Register expected data and immediately check any already-written bytes. Matched bytes are consumed from both sides. Returns an error if existing data does not match. |
|
Clear all data, expected data, and reset |
Putting It Together
The following snippet tests a request-handler coroutine that reads a
fixed-size request from a ReadSource, processes it, and writes the
response to a WriteSink. The fuse.armed() loop exercises every
error site in both the read and write paths.
#include <boost/capy/buffers/make_buffer.hpp>
#include <boost/capy/concept/read_source.hpp>
#include <boost/capy/concept/write_sink.hpp>
#include <boost/capy/cond.hpp>
#include <boost/capy/task.hpp>
#include <boost/capy/test/fuse.hpp>
#include <boost/capy/test/read_source.hpp>
#include <boost/capy/test/write_sink.hpp>
using namespace boost::capy;
using namespace boost::capy::test;
// Function under test: echo the request back as the response
template<ReadSource Source, WriteSink Sink>
task<std::error_code>
handle_request(Source& source, Sink& sink)
{
char buf[64];
auto [ec, n] = co_await source.read(
mutable_buffer(buf, sizeof(buf)));
if(ec && ec != cond::eof)
co_return ec;
auto [ec2, n2] = co_await sink.write(
const_buffer(buf, n));
if(ec2)
co_return ec2;
auto [ec3] = co_await sink.write_eof();
if(ec3)
co_return ec3;
co_return std::error_code{};
}
void test_handle_request()
{
fuse f;
auto r = f.armed([&](fuse&) -> task<void> {
read_source rs(f);
write_sink ws(f);
rs.provide("ping");
auto ec = co_await handle_request(rs, ws);
if(ec)
co_return; // fuse injected an error; exit gracefully
BOOST_TEST(ws.data() == "ping");
BOOST_TEST(ws.eof_called());
});
BOOST_TEST(r.success);
}
Reference
| Header | Contents |
|---|---|
|
Mock ReadSource with complete reads. |
|
Mock WriteSink with complete writes and explicit EOF. |
Continue to Mock Buffer Sources and Sinks.