Files
scylladb/test/boost/small_vector_test.cc
Kefu Chai 372a4d1b79 treewide: do not define FMT_DEPRECATED_OSTREAM
since we do not rely on FMT_DEPRECATED_OSTREAM to define the
fmt::formatter for us anymore, let's stop defining `FMT_DEPRECATED_OSTREAM`.

in this change,

* utils: drop the range formatters in to_string.hh and to_string.c, as
  we don't use them anymore. and the tests for them in
  test/boost/string_format_test.cc are removed accordingly.
* utils: use fmt to print chunk_vector and small_vector. as
  we are not able to print the elements using operator<< anymore
  after switching to {fmt} formatters.
* test/boost: specialize fmt::details::is_std_string_like<bytes>
  due to a bug in {fmt} v9, {fmt} fails to format a range whose
  element type is `basic_sstring<uint8_t>`, as it considers it
  as a string-like type, but `basic_sstring<uint8_t>`'s char type
  is signed char, not char. this issue does not exist in {fmt} v10,
  so, in this change, we add a workaround to explicitly specialize
  the type trait to assure that {fmt} format this type using its
  `fmt::formatter` specialization instead of trying to format it
  as a string. also, {fmt}'s generic ranges formatter calls the
  pair formatter's `set_brackets()` and `set_separator()` methods
  when printing the range, but operator<< based formatter does not
  provide these method, we have to include this change in the change
  switching to {fmt}, otherwise the change specializing
  `fmt::details::is_std_string_like<bytes>` won't compile.
* test/boost: in tests, we use `BOOST_REQUIRE_EQUAL()` and its friends
  for comparing values. but without the operator<< based formatters,
  Boost.Test would not be able to print them. after removing
  the homebrew formatters, we need to use the generic
  `boost_test_print_type()` helper to do this job. so we are
  including `test_utils.hh` in tests so that we can print
  the formattable types.
* treewide: add "#include "utils/to_string.hh" where
  `fmt::formatter<optional<>>` is used.
* configure.py: do not define FMT_DEPRECATED_OSTREAM
* cmake: do not define FMT_DEPRECATED_OSTREAM

Refs #13245

Signed-off-by: Kefu Chai <kefu.chai@scylladb.com>
2024-04-19 22:57:36 +08:00

439 lines
12 KiB
C++

/*
* Copyright (C) 2018-present ScyllaDB
*/
/*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#define BOOST_TEST_MODULE small_vector
#include <boost/test/included/unit_test.hpp>
#include <fmt/ranges.h>
#include "utils/small_vector.hh"
template<typename T, size_t N>
void check_equivalent(const utils::small_vector<T, N>& a, const std::vector<T>& b) {
BOOST_REQUIRE_EQUAL(a.size(), b.size());
BOOST_REQUIRE_GE(a.capacity(), a.size());
for (auto i = 0u; i < b.size(); i++) {
BOOST_REQUIRE(a[i] == b[i]);
}
auto idx = 0;
for (auto&& v : a) {
BOOST_REQUIRE(v == b[idx++]);
}
}
template<typename Iterator>
class input_iterator_wrapper {
Iterator _it;
public:
using value_type = typename std::iterator_traits<Iterator>::value_type;
using pointer = typename std::iterator_traits<Iterator>::pointer;
using reference = typename std::iterator_traits<Iterator>::reference;
using difference_type = typename std::iterator_traits<Iterator>::difference_type;
using iterator_category = std::input_iterator_tag;
input_iterator_wrapper(Iterator it) : _it(it) { }
value_type operator*() const { return *_it; }
input_iterator_wrapper& operator++() {
++_it;
return *this;
}
input_iterator_wrapper operator++(int) {
auto it = *this;
operator++();
return it;
}
bool operator==(const input_iterator_wrapper& other) const = default;
};
template<typename T>
void test_random_walk(std::function<T()> make_element) {
utils::small_vector<T, 8> actual;
std::vector<T> expected;
auto emplace_back = [&] (T x) {
actual.emplace_back(x);
expected.emplace_back(x);
check_equivalent(actual, expected);
};
auto test_range_ctor = [&] (utils::small_vector<T, 8>& actual) {
auto a1 = utils::small_vector<T, 8>(actual.begin(), actual.end());
check_equivalent(a1, expected);
auto a2 = utils::small_vector<T, 8>(input_iterator_wrapper(actual.begin()), input_iterator_wrapper(actual.end()));
check_equivalent(a2, expected);
};
auto move_ctor = [&] (utils::small_vector<T, 8>& actual) {
auto a = std::move(actual);
check_equivalent(a, expected);
return a;
};
auto copy_ctor = [&] (utils::small_vector<T, 8>& actual) {
auto a = actual;
check_equivalent(a, expected);
return a;
};
auto move_assign = [&] (utils::small_vector<T, 8>& src, utils::small_vector<T, 8>& dst) {
dst = std::move(src);
check_equivalent(dst, expected);
};
auto copy_assign = [&] (utils::small_vector<T, 8>& src, utils::small_vector<T, 8>& dst) {
dst = src;
check_equivalent(dst, expected);
};
for (auto i = 0; i < 64; i++) {
emplace_back(make_element());
test_range_ctor(actual);
auto a1 = copy_ctor(actual);
auto a2 = move_ctor(actual);
// Assigning to moved-from containers
move_assign(a1, actual);
copy_assign(a2, a1);
// Assigning to a container with other elements
copy_assign(a2, actual);
move_assign(a2, actual);
// Assigning to a container with pre-existing elements, forcing slow
// path
auto a3 = utils::small_vector<T, 8>();
for (auto j = 0; j <= i / 2; j++) {
a3.emplace_back();
}
copy_assign(a1, a3);
}
auto another_actual = utils::small_vector<T, 8>(expected.begin(), expected.end());
check_equivalent(another_actual, expected);
for (auto i = 0u; i <= actual.size(); i++) {
auto test_range_insertion = [&] (auto first, auto last) {
auto a1 = actual;
auto a2 = actual;
check_equivalent(a1, expected);
check_equivalent(a2, expected);
auto e = expected;
a1.insert(a1.begin() + i, first, last);
a2.insert(a2.begin() + i, input_iterator_wrapper(first), input_iterator_wrapper(last));
e.insert(e.begin() + i, first, last);
check_equivalent(a1, e);
check_equivalent(a2, e);
};
test_range_insertion(actual.begin(), actual.begin());
test_range_insertion(actual.begin(), actual.end());
test_range_insertion(actual.begin(), actual.begin() + 1);
test_range_insertion(actual.begin(), actual.begin() + 4);
{
auto a = actual;
auto e = expected;
a.insert(a.begin() + i, actual[0]);
e.insert(e.begin() + i, actual[0]);
check_equivalent(a, e);
}
{
auto a = actual;
auto e = expected;
a.erase(a.begin(), a.begin() + i);
e.erase(e.begin(), e.begin() + i);
check_equivalent(a, e);
}
if (i < actual.size()) {
auto a = actual;
auto e = expected;
a.erase(a.begin() + i, a.end());
e.erase(e.begin() + i, e.end());
check_equivalent(a, e);
}
if (i < actual.size()) {
auto a = actual;
auto e = expected;
a.erase(a.begin() + i);
e.erase(e.begin() + i);
check_equivalent(a, e);
}
}
}
BOOST_AUTO_TEST_CASE(random_walk_trivial) {
test_random_walk<int>([x = 0] () mutable { return x++; });
}
BOOST_AUTO_TEST_CASE(random_walk_nontrivial) {
test_random_walk<std::shared_ptr<int>>([x = 0] () mutable { return std::make_shared<int>(x++); });
}
template<typename T>
void test_insert(std::function<T()> make_element) {
utils::small_vector<T, 8> actual;
std::vector<T> expected;
auto emplace_back = [&] {
auto e = make_element();
actual.emplace_back(e);
expected.emplace_back(e);
};
auto insert_middle = [&] (size_t count) {
auto a = actual;
auto e = expected;
check_equivalent(a, e);
std::vector<T> vec;
std::generate_n(std::back_inserter(vec), count, make_element);
a.insert(a.begin() + 1, vec.begin(), vec.end());
e.insert(e.begin() + 1, vec.begin(), vec.end());
check_equivalent(a, e);
};
auto insert_end = [&] (size_t count) {
auto a = actual;
auto e = expected;
check_equivalent(a, e);
std::vector<T> vec;
std::generate_n(std::back_inserter(vec), count, make_element);
a.insert(a.end(), vec.begin(), vec.end());
e.insert(e.end(), vec.begin(), vec.end());
check_equivalent(a, e);
};
auto test_inserts = [&] {
insert_middle(2);
insert_middle(4);
insert_middle(6);
insert_middle(8);
insert_middle(64);
insert_end(2);
insert_end(4);
insert_end(6);
insert_end(8);
insert_end(64);
};
for (auto i = 0u; i < 2u; i++) {
emplace_back();
}
test_inserts();
for (auto i = 0u; i < 2u; i++) {
emplace_back();
}
test_inserts();
for (auto i = 0u; i < 4u; i++) {
emplace_back();
}
test_inserts();
}
BOOST_AUTO_TEST_CASE(insert_trivial) {
test_insert<int>([x = 0] () mutable { return x++; });
}
BOOST_AUTO_TEST_CASE(insert_nontrivial) {
test_insert<std::shared_ptr<int>>([x = 0] () mutable { return std::make_shared<int>(x++); });
}
namespace {
class fails_on_copy {
size_t _counter;
public:
static inline thread_local size_t live = 0;
public:
explicit fails_on_copy(size_t counter) : _counter(counter) {
live++;
}
~fails_on_copy() {
// Make sure the compiler doesn't do anything clever
*reinterpret_cast<volatile size_t*>(&_counter) = -1;
live--;
}
fails_on_copy(fails_on_copy&& other) noexcept : _counter(other._counter) {
live++;
}
fails_on_copy(const fails_on_copy& other) : _counter(other._counter - 1) {
if (!_counter) {
throw 1;
}
live++;
}
fails_on_copy& operator=(fails_on_copy&&) = default;
fails_on_copy& operator=(const fails_on_copy& other) {
_counter = other._counter - 1;
if (!_counter) {
throw 1;
}
return *this;
}
size_t counter() const { return _counter; }
};
}
BOOST_AUTO_TEST_CASE(exception_safety) {
std::vector<fails_on_copy> vec;
vec.emplace_back(4);
vec.emplace_back(1);
BOOST_REQUIRE_THROW((utils::small_vector<fails_on_copy, 1>(vec.begin(), vec.end())), int);
BOOST_REQUIRE_THROW((utils::small_vector<fails_on_copy, 4>(vec.begin(), vec.end())), int);
utils::small_vector<fails_on_copy, 2> v;
v.emplace_back(0);
v.emplace_back(1);
v.emplace_back(2);
v.emplace_back(3);
auto verify_unchanged = [&] {
BOOST_REQUIRE_EQUAL(v.size(), 4);
for (auto i = 0; i < 4; i++) {
BOOST_REQUIRE_EQUAL(v[i].counter(), i);
}
};
BOOST_REQUIRE_THROW(v.insert(v.begin(), vec.begin(), vec.end()), int);
verify_unchanged();
BOOST_REQUIRE_THROW(v.insert(v.end(), vec.begin(), vec.end()), int);
verify_unchanged();
vec.emplace_back(4);
vec.emplace_back(4);
vec.emplace_back(4);
vec.emplace_back(4);
BOOST_REQUIRE_THROW(v.insert(v.begin(), vec.begin(), vec.end()), int);
verify_unchanged();
vec.clear();
vec.emplace_back(4);
vec.emplace_back(4);
vec.emplace_back(4);
vec.emplace_back(4);
vec.emplace_back(4);
vec.emplace_back(1);
BOOST_REQUIRE_THROW(v.insert(v.begin(), vec.begin(), vec.end()), int);
verify_unchanged();
{
auto fc = fails_on_copy(1);
BOOST_REQUIRE_THROW(v.insert(v.begin(), fc), int);
verify_unchanged();
fc = fails_on_copy(1);
BOOST_REQUIRE_THROW(v.insert(v.end(), fc), int);
verify_unchanged();
fc = fails_on_copy(1);
BOOST_REQUIRE_THROW(v.push_back(fc), int);
verify_unchanged();
}
utils::small_vector<fails_on_copy, 2> v2;
v2.emplace_back(4);
v2.emplace_back(1);
BOOST_REQUIRE_THROW(v = v2, int);
verify_unchanged();
v2.emplace_back(4);
v2.emplace_back(4);
BOOST_REQUIRE_THROW(v = v2, int);
verify_unchanged();
v2.clear();
v.clear();
vec.clear();
BOOST_REQUIRE_EQUAL(fails_on_copy::live, 0);
}
BOOST_AUTO_TEST_CASE(compare) {
{
auto lhs = utils::small_vector<int, 4>({1, 2, 3, 4});
auto rhs = utils::small_vector<int, 4>({1, 3, 3, 4});
BOOST_CHECK_LT(lhs, rhs);
}
{
auto lhs = utils::small_vector<int, 4>({1, 2, 3, 4});
auto rhs = utils::small_vector<int, 4>({1, 3, 4});
BOOST_CHECK_LT(lhs, rhs);
}
{
auto lhs = utils::small_vector<int, 4>({1, 2, 3, 4});
auto rhs = utils::small_vector<int, 4>({1, 3, 4});
BOOST_CHECK_LT(lhs, rhs);
}
{
auto lhs = utils::small_vector<int, 4>({4, 2, 1});
auto rhs = utils::small_vector<int, 4>({1, 3, 3, 4});
BOOST_CHECK_GT(lhs, rhs);
}
{
auto lhs = utils::small_vector<int, 4>({4, 2, 1});
auto rhs = utils::small_vector<int, 4>({1, 3, 3, 4});
BOOST_CHECK_GE(lhs, rhs);
}
}
BOOST_AUTO_TEST_CASE(resize) {
auto vec = utils::small_vector<int, 4>();
vec.emplace_back(1);
vec.resize(1024);
BOOST_REQUIRE_EQUAL(vec.size(), 1024);
BOOST_REQUIRE_EQUAL(vec[0], 1);
for (auto i = 0; i < 1023; i++) {
BOOST_REQUIRE_EQUAL(vec[i + 1], 0);
}
vec.resize(1024);
BOOST_REQUIRE_EQUAL(vec.size(), 1024);
BOOST_REQUIRE_EQUAL(vec[0], 1);
for (auto i = 0; i < 1023; i++) {
BOOST_REQUIRE_EQUAL(vec[i + 1], 0);
}
vec.resize(512);
BOOST_REQUIRE_EQUAL(vec.size(), 512);
BOOST_REQUIRE_EQUAL(vec[0], 1);
for (auto i = 0; i < 511; i++) {
BOOST_REQUIRE_EQUAL(vec[i + 1], 0);
}
vec.resize(0);
BOOST_REQUIRE_EQUAL(vec.size(), 0);
for (auto&& v : vec) {
(void)v;
BOOST_FAIL("should not reach");
}
}