Buffer Inspection

Two small utilities round out the toolkit. bufgrind iterates every split point of a buffer sequence, exercising every chunk-boundary condition in the system under test. buffer_to_string concatenates buffer sequences into a std::string for assertion.

bufgrind

bufgrind is a test utility that iterates through every way to split a buffer sequence into two contiguous pieces. For an N-byte input it produces N+1 split positions: (0, N), (1, N-1), …​, (N, 0). The two pieces at each position concatenate back to the original sequence. Any code that processes a buffer in chunks is exercised at every possible chunk boundary with a single while loop.

bufgrind does not perform I/O and does not consult a fuse, so the snippets on this page drive it under f.inert(…​). A single pass is sufficient to visit every split position, and there are no async failure sites to inject.

#include <boost/capy/test/bufgrind.hpp>
#include <boost/capy/test/buffer_to_string.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_all_splits()
{
    std::string data = "hello";
    auto cb = make_buffer(data);

    fuse f;
    auto r = f.inert([&](fuse&) -> task<> {
        bufgrind bg(cb);
        while(bg)
        {
            auto [b1, b2] = co_await bg.next();
            BOOST_TEST_EQ(buffer_to_string(b1, b2), data);
        }
    });
    BOOST_TEST(r.success);
}

Iteration Pattern

For a 5-byte input "hello", bufgrind yields six positions:

pos=0: b1=""      b2="hello"
pos=1: b1="h"     b2="ello"
pos=2: b1="he"    b2="llo"
pos=3: b1="hel"   b2="lo"
pos=4: b1="hell"  b2="o"
pos=5: b1="hello" b2=""

An empty buffer sequence yields one position where both pieces are empty, so the loop body always executes at least once.

Step Size

When the input is large, visiting every byte boundary is expensive. Pass a step parameter to skip positions. The final position (equal to the total size) is always visited regardless of step alignment.

std::string data = "0123456789";  // 10 bytes
auto cb = make_buffer(data);

bufgrind bg(cb, 3);
// Visits positions: 0, 3, 6, 9, 10
while(bg)
{
    auto [b1, b2] = co_await bg.next();
    // exercise parser at each split point
}

A step of 0 is treated as 1. A step larger than the total size reduces to two positions: 0 and size.

Mutability Preservation

bufgrind is templated on a ConstBufferSequence but the slices it produces follow the mutability of the input. Each half is itself a buffer sequence (slice_type). Passing a mutable_buffer yields halves that model MutableBufferSequence; passing a const_buffer yields halves that model ConstBufferSequence. This matters for tests that need to write into the produced buffers rather than only read from them.

char data[] = "hello";
mutable_buffer mb(data, 5);

bufgrind bg(mb);
while(bg)
{
    auto [b1, b2] = co_await bg.next();
    // each half is itself a buffer sequence; a mutable input
    // yields MutableBufferSequence halves callers may write into
    static_assert(MutableBufferSequence<decltype(b1)>);
}

See test::bufgrind for the complete member list: the constructor’s step parameter, operator bool, and next.

buffer_to_string

buffer_to_string concatenates one or more buffer sequences into a std::string. With a single argument it converts that buffer sequence; with multiple arguments it concatenates them in order. The most common use is asserting the combined content of a bufgrind split.

#include <boost/capy/test/buffer_to_string.hpp>
#include <boost/capy/buffers/make_buffer.hpp>

using namespace boost::capy;
using namespace boost::capy::test;

void test_buffer_to_string()
{
    // Single buffer sequence
    const_buffer cb(make_buffer(std::string_view("hello")));
    BOOST_TEST_EQ(buffer_to_string(cb), "hello");

    // Multiple buffer sequences concatenated in order
    const_buffer b1(make_buffer(std::string_view("hello")));
    const_buffer b2(make_buffer(std::string_view(" world")));
    BOOST_TEST_EQ(buffer_to_string(b1, b2), "hello world");
}

Use With bufgrind

The typical pattern passes both halves of a bufgrind split directly to buffer_to_string to verify that each split reconstructs the original input:

std::string original = "hello world";
auto cb = make_buffer(original);

fuse f;
auto r = f.inert([&](fuse&) -> task<> {
    bufgrind bg(cb);
    while(bg)
    {
        auto [b1, b2] = co_await bg.next();
        BOOST_TEST_EQ(buffer_to_string(b1, b2), original);
    }
});
BOOST_TEST(r.success);

See test::buffer_to_string for the exact signature.

Putting It Together

The following snippet tests a hypothetical parser that reads from a read_stream. bufgrind exercises every split of the input so the parser is run against every possible chunk boundary; buffer_to_string verifies the output at each split:

#include <boost/capy/test/bufgrind.hpp>
#include <boost/capy/test/buffer_to_string.hpp>
#include <boost/capy/test/fuse.hpp>
#include <boost/capy/test/read_stream.hpp>
#include <boost/capy/buffers/make_buffer.hpp>
#include <boost/capy/task.hpp>

#include <array>
#include <string>

using namespace boost::capy;
using namespace boost::capy::test;

// Hypothetical parser: reads all bytes from a ReadStream
task<std::string> read_all(read_stream& rs)
{
    std::string out;
    std::array<char, 64> buf;
    for(;;)
    {
        auto [ec, n] = co_await rs.read_some(make_buffer(buf));
        if(ec)
            co_return out;
        out.append(buf.data(), n);
    }
}

void test_parser_all_splits()
{
    std::string input = "GET / HTTP/1.1\r\n";
    auto cb = make_buffer(input);

    fuse f;
    auto r = f.inert([&](fuse&) -> task<> {
        bufgrind bg(cb);
        while(bg)
        {
            auto [b1, b2] = co_await bg.next();

            // Feed the split as two discrete reads
            read_stream rs(f);
            rs.provide(buffer_to_string(b1));
            rs.provide(buffer_to_string(b2));

            std::string got = co_await read_all(rs);
            BOOST_TEST_EQ(got, input);
        }
    });
    BOOST_TEST(r.success);
}

See Reference for the full API browser.