Files
scylladb/test/boost/intrusive_array_test.cc
Pavel Emelyanov eb70644c1c intrusive-array: Array with trusted bounds
A plain array of elements that grows and shrinks by
constructing the new instance from an existing one and
moving the elements from it.

Behaves similarly to vector's external array, but has
0-bytes overhead. The array bounds (0-th and N-th
elemements) are determined by checking the flags on the
elements themselves. For this the type must support
getters and setters for the flags.

To remove an element from array there's also a nothrow
option that drops the requested element from array,
shifts the righter ones left and keeps the trailing
unused memory (so called "train") until reconstruction
or destruction.

Also comes with lower_bound() helper that helps keeping
the elements sotred and the from_element() one that
returns back reference to the array in which the element
sits.

Signed-off-by: Pavel Emelyanov <xemul@scylladb.com>
2020-07-14 16:29:49 +03:00

244 lines
7.6 KiB
C++

/*
* Copyright (C) 2020 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/>.
*/
#include <boost/test/unit_test.hpp>
#include <seastar/testing/thread_test_case.hh>
#include <fmt/core.h>
#include "utils/intrusive-array.hh"
#include "utils/logalloc.hh"
class element {
bool _head = false;
bool _tail = false;
bool _train = false;
long _data;
int *_cookie;
int *_cookie2;
public:
explicit element(long val) : _data(val), _cookie(new int(0)), _cookie2(new int(0)) { }
element(const element& other) = delete;
element(element&& other) noexcept : _head(other._head), _tail(other._tail), _train(other._train),
_data(other._data), _cookie(other._cookie), _cookie2(new int(0)) {
other._cookie = nullptr;
}
~element() {
if (_cookie != nullptr) {
delete _cookie;
}
delete _cookie2;
}
bool is_head() const noexcept { return _head; }
void set_head(bool v) noexcept { _head = v; }
bool is_tail() const noexcept { return _tail; }
void set_tail(bool v) noexcept { _tail = v; }
bool with_train() const noexcept { return _train; }
void set_train(bool v) noexcept { _train = v; }
bool operator==(long v) const { return v == _data; }
long operator*() const { return _data; }
bool bound_check(int idx, int size) {
return ((idx == 0) == is_head()) && ((idx == size - 1) == is_tail());
}
};
using test_array = intrusive_array<element>;
static bool size_check(test_array& a, size_t size, unsigned short tlen) {
return a[size - 1].is_tail() && a.size() == size &&
size_for_allocation_strategy(a) == (size + tlen) * sizeof(element) &&
((tlen != 0) == a[0].with_train()) &&
((tlen == 0) || *reinterpret_cast<unsigned short*>(&a[size]) == tlen);
}
void show(const char *pfx, test_array& a, int sz) {
int i;
fmt::print("{}", pfx);
for (i = 0; i < sz; i++) {
fmt::print("{}{}{}", a[i].is_head() ? 'H' : ' ', *a[i], a[i].is_tail() ? 'T' : ' ');
}
if (a[0].with_train()) {
fmt::print(" ~{}", *reinterpret_cast<unsigned short *>(&a[i]));
}
fmt::print("\n");
}
SEASTAR_THREAD_TEST_CASE(test_basic_construct) {
test_array array(12);
for (auto i = array.begin(); i != array.end(); i++) {
BOOST_REQUIRE(*i == 12);
}
}
test_array* grow(test_array& from, size_t nsize, int npos, long ndat) {
BOOST_REQUIRE(from.size() + 1 == nsize);
auto ptr = current_allocator().alloc(&get_standard_migrator<test_array>(), sizeof(element) * nsize, alignof(test_array));
return new (ptr) test_array(from, test_array::grow_tag{npos}, ndat);
}
test_array* shrink(test_array& from, size_t nszie, int spos) {
BOOST_REQUIRE(from.size() - 1 == nszie);
auto ptr = current_allocator().alloc(&get_standard_migrator<test_array>(), sizeof(element) * nszie, alignof(test_array));
return new (ptr) test_array(from, test_array::shrink_tag{spos});
}
void grow_shrink_and_check(test_array& cur, int size, int depth) {
for (int i = 0; i <= size; i++) {
long nel = size + 12;
test_array* narr = grow(cur, size + 1, i, nel);
int idx = 0;
BOOST_REQUIRE(size_check(*narr, size + 1, 0));
for (auto ni = narr->begin(); ni != narr->end(); ni++) {
if (idx == i) {
BOOST_REQUIRE(*ni == nel);
} else if (idx < i) {
BOOST_REQUIRE(*ni == *cur[idx]);
} else {
BOOST_REQUIRE(*ni == *cur[idx - 1]);
}
BOOST_REQUIRE(ni->bound_check(idx, size + 1));
idx++;
}
if (size < depth) {
grow_shrink_and_check(*narr, size + 1, depth);
}
current_allocator().destroy(narr);
}
if (size > 1) {
for (int i = 0; i < size; i++) {
test_array* narr = shrink(cur, size - 1, i);
int idx = 0;
BOOST_REQUIRE(size_check(*narr, size - 1, 0));
for (auto ni = narr->begin(); ni != narr->end(); ni++) {
if (idx == i) {
continue;
} else if (idx < i) {
BOOST_REQUIRE(*ni == *cur[idx]);
} else {
BOOST_REQUIRE(*ni == *cur[idx + 1]);
}
BOOST_REQUIRE(ni->bound_check(idx, size - 1));
idx++;
}
current_allocator().destroy(narr);
}
}
}
SEASTAR_THREAD_TEST_CASE(test_grow_shrink_construct) {
test_array array(12);
grow_shrink_and_check(array, 1, 5);
}
SEASTAR_THREAD_TEST_CASE(test_erase) {
test_array a1(10);
test_array *a2 = grow(a1, 2, 1, 20);
test_array *a3 = grow(*a2, 3, 2, 30);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 2; k++) {
std::vector<int> x({10, 20, 30, 40});
test_array *a4 = grow(*a3, 4, 3, 40);
auto test_fn = [&] (int idx, int sz) {
a4->erase(idx);
x.erase(x.begin() + idx);
BOOST_REQUIRE(size_check(*a4, sz, 4 - sz));
for (int a = 0; a < sz; a++) {
BOOST_REQUIRE(x[a] == *(*a4)[a]);
}
};
test_fn(i, 3);
test_fn(j, 2);
test_fn(k, 1);
current_allocator().destroy(a4);
}
}
}
current_allocator().destroy(a3);
current_allocator().destroy(a2);
}
SEASTAR_THREAD_TEST_CASE(test_lower_bound) {
test_array a1(12);
struct compare {
int operator()(const element& a, const element& b) const { return *a - *b; }
};
test_array *a2 = grow(a1, 2, 1, 14);
auto i = a2->lower_bound(element(13), compare{});
BOOST_REQUIRE(*i == 14 && a2->index_of(i) == 1);
test_array *a3 = grow(*a2, 3, 2, 17);
bool match;
BOOST_REQUIRE(*a3->lower_bound(element(11), compare{}, match) == 12 && !match);
BOOST_REQUIRE(*a3->lower_bound(element(12), compare{}, match) == 12 && match);
BOOST_REQUIRE(*a3->lower_bound(element(13), compare{}, match) == 14 && !match);
BOOST_REQUIRE(*a3->lower_bound(element(14), compare{}, match) == 14 && match);
BOOST_REQUIRE(*a3->lower_bound(element(15), compare{}, match) == 17 && !match);
BOOST_REQUIRE(*a3->lower_bound(element(16), compare{}, match) == 17 && !match);
BOOST_REQUIRE(*a3->lower_bound(element(17), compare{}, match) == 17 && match);
BOOST_REQUIRE(a3->lower_bound(element(18), compare{}, match) == a3->end());
current_allocator().destroy(a3);
current_allocator().destroy(a2);
}
SEASTAR_THREAD_TEST_CASE(test_from_element) {
test_array a1(12);
test_array *a2 = grow(a1, 2, 1, 14);
test_array *a3 = grow(*a2, 3, 2, 17);
element* i = &((*a3)[2]);
BOOST_REQUIRE(*i == 17);
int idx;
test_array& x = test_array::from_element(i, idx);
BOOST_REQUIRE(&x == a3 && idx == 2);
current_allocator().destroy(a3);
current_allocator().destroy(a2);
}