We use boost test logging primarily to generate nice XML xunit files used in Jenkins. These XML files can be bloated with messages from BOOST_TEST_MESSAGE(), hundreds of megabytes of build archives, on every build. Let's use seastar logger for test logging instead, reserving the use of boost log facilities for boost test markup information.
246 lines
8.0 KiB
C++
246 lines
8.0 KiB
C++
/*
|
|
* Copyright (C) 2019 ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* This file is part of Scylla.
|
|
*
|
|
* Scylla is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Scylla is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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());
|
|
}
|
|
}
|