Files
scylladb/test/boost/linearizing_input_stream_test.cc
Avi Kivity f3eade2f62 treewide: relicense to ScyllaDB-Source-Available-1.0
Drop the AGPL license in favor of a source-available license.
See the blog post [1] for details.

[1] https://www.scylladb.com/2024/12/18/why-were-moving-to-a-source-available-license/
2024-12-18 17:45:13 +02:00

233 lines
7.4 KiB
C++

/*
* Copyright (C) 2019-present ScyllaDB
*/
/*
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
*/
#define BOOST_TEST_MODULE core
#include <random>
#include <boost/test/unit_test.hpp>
#include "bytes_ostream.hh"
#include "utils/linearizing_input_stream.hh"
#include "utils/serialization.hh"
#include "test/lib/random_utils.hh"
#include "test/lib/log.hh"
namespace {
class fragment_vector {
public:
using fragment_type = bytes_view;
using iterator = std::vector<fragment_type>::const_iterator;
using const_iterator = std::vector<fragment_type>::const_iterator;
private:
std::vector<bytes> _fragments;
std::vector<fragment_type> _fragment_views;
size_t _size = 0;
public:
explicit fragment_vector(std::vector<bytes> fragments)
: _fragments(std::move(fragments)) {
for (const auto& frag : _fragments) {
_fragment_views.emplace_back(frag);
_size += frag.size();
}
}
const_iterator begin() const {
return _fragment_views.begin();
}
const_iterator end() const {
return _fragment_views.end();
}
size_t fragments() const {
return _fragments.size();
}
size_t size_bytes() const {
return _size;
}
bool empty() const {
return _size == 0;
}
};
struct value_description {
virtual ~value_description() = default;
virtual size_t size() const = 0;
virtual void write(bytes_ostream::output_iterator&) const = 0;
virtual void skip(utils::linearizing_input_stream<fragment_vector>&) const = 0;
virtual void read_and_check_value(utils::linearizing_input_stream<fragment_vector>&) const = 0;
};
struct payload {
bytes data;
std::vector<std::unique_ptr<value_description>> value_descriptions;
};
template <typename T>
T get_int(std::mt19937& rnd_engine) {
return tests::random::get_int(std::numeric_limits<T>::min(), std::numeric_limits<T>::max(), rnd_engine);
}
template <typename T>
class trivial_value_description : public value_description {
T _value;
public:
explicit trivial_value_description(T v) : _value(v) {
}
virtual size_t size() const override {
return sizeof(T);
}
virtual void write(bytes_ostream::output_iterator& out) const override {
::write(out, _value);
}
virtual void skip(utils::linearizing_input_stream<fragment_vector>& in) const override {
in.skip(sizeof(T));
}
virtual void read_and_check_value(utils::linearizing_input_stream<fragment_vector>& in) const override {
const auto v = in.read_trivial<T>();
BOOST_REQUIRE_EQUAL(_value, v);
}
};
class string_value_description : public value_description {
sstring _value;
public:
explicit string_value_description(std::mt19937& rnd_engine, size_t size)
// We know size is at least 3
: _value(tests::random::get_sstring(size - serialize_int16_size, rnd_engine)) {
}
virtual size_t size() const override {
return ::serialize_string_size(_value);
}
virtual void write(bytes_ostream::output_iterator& out) const override {
serialize_string(out, _value);
}
virtual void skip(utils::linearizing_input_stream<fragment_vector>& in) const override {
in.skip(size());
}
virtual void read_and_check_value(utils::linearizing_input_stream<fragment_vector>& in) const override {
const auto size = in.read_trivial<uint16_t>();
BOOST_REQUIRE_EQUAL(size, _value.size());
const auto v = sstring(reinterpret_cast<const sstring::value_type*>(in.read(size).data()), size);
BOOST_REQUIRE_EQUAL(_value, v);
}
};
const payload generate_payload(std::mt19937& rnd_engine) {
bytes_ostream ret;
auto out = ret.write_begin();
std::vector<std::unique_ptr<value_description>> value_descriptions;
size_t total_size = 0;
auto value_size_dist = tests::random::stepped_int_distribution<size_t>{{
{50.0, {1, 8}},
{50.0, {9, 100}}}};
for (size_t i = 0; i < 100; ++i) {
const auto size = value_size_dist(rnd_engine);
std::unique_ptr<value_description> vd;
switch (size) {
case 1:
vd = std::make_unique<trivial_value_description<uint8_t>>(get_int<int8_t>(rnd_engine));
break;
case 2:
vd = std::make_unique<trivial_value_description<int16_t>>(get_int<int16_t>(rnd_engine));
break;
case 4:
vd = std::make_unique<trivial_value_description<int32_t>>(get_int<int32_t>(rnd_engine));
break;
case 8:
vd = std::make_unique<trivial_value_description<int64_t>>(get_int<int64_t>(rnd_engine));
break;
default:
vd = std::make_unique<string_value_description>(rnd_engine, size);
break;
}
BOOST_REQUIRE_EQUAL(vd->size(), size);
total_size += size;
vd->write(out);
BOOST_REQUIRE_EQUAL(ret.size(), total_size);
value_descriptions.emplace_back(std::move(vd));
}
return payload{bytes(ret.linearize()), std::move(value_descriptions)};
}
std::vector<bytes> no_fragmenting(std::mt19937&, bytes_view bv) {
testlog.info("Fragmenting payload with {}()", __FUNCTION__);
return {bytes{bv}};
}
template <size_t N>
std::vector<bytes> n_byte_fragments(std::mt19937&, bytes_view bv) {
testlog.info("Fragmenting payload with {}<{}>()", __FUNCTION__, N);
std::vector<bytes> ret;
while (!bv.empty()) {
const auto size = std::min(bv.size(), N);
ret.emplace_back(bytes_view(bv.begin(), size));
bv.remove_prefix(size);
}
return ret;
}
std::vector<bytes> random_fragments(std::mt19937& rnd_engine, bytes_view bv) {
testlog.info("Fragmenting payload with {}()", __FUNCTION__);
std::vector<bytes> ret;
while (!bv.empty()) {
std::uniform_int_distribution<size_t> size_dist{1, bv.size()};
const auto size = size_dist(rnd_engine);
ret.emplace_back(bytes_view(bv.begin(), size));
bv.remove_prefix(size);
}
return ret;
}
}
BOOST_AUTO_TEST_CASE(test_linearizing_input_stream) {
// REPLACE RANDOM SEED HERE.
const auto seed = std::random_device{}();
std::cout << "test seed: " << seed << std::endl;
auto rnd_engine = std::mt19937(seed);
auto payload = generate_payload(rnd_engine);
std::uniform_int_distribution<uint8_t> skip_dist{0, 1};
testlog.info("Read back data");
for (auto&& fragment_fn : {no_fragmenting, n_byte_fragments<1>, n_byte_fragments<2>, n_byte_fragments<3>, random_fragments}) {
size_t expected_size = payload.data.size();
auto fragmented_payload = fragment_vector(fragment_fn(rnd_engine, payload.data));
auto in = utils::linearizing_input_stream(fragmented_payload);
testlog.info("Testing with payload: size={}, fragments={}", expected_size, fragmented_payload.fragments());
BOOST_REQUIRE_EQUAL(fragmented_payload.size_bytes(), expected_size);
BOOST_REQUIRE_EQUAL(in.size(), expected_size);
for (auto&& value_description : payload.value_descriptions) {
if (skip_dist(rnd_engine)) {
value_description->skip(in);
} else {
value_description->read_and_check_value(in);
}
expected_size -= value_description->size();
BOOST_REQUIRE_EQUAL(in.size(), expected_size);
}
BOOST_REQUIRE_EQUAL(in.size(), 0);
BOOST_REQUIRE(in.empty());
}
}