241 lines
6.9 KiB
C++
241 lines
6.9 KiB
C++
#include <cruft/util/pool.hpp>
|
|
|
|
#include <cruft/util/tap.hpp>
|
|
#include <cruft/util/random.hpp>
|
|
|
|
#include <set>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
check_single (cruft::TAP::logger &tap)
|
|
{
|
|
// Ensure a single element doesn't break the circular linked list
|
|
cruft::pool<uint64_t> single(1);
|
|
tap.expect_nothrow ([&] {
|
|
single.deallocate (single.allocate ());
|
|
}, "single element acquire-release");
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
check_unique_ptr (cruft::TAP::logger &tap)
|
|
{
|
|
constexpr std::size_t element_count = 1025;
|
|
cruft::pool<uint64_t> uintpool (element_count);
|
|
std::set<uint64_t *> uintset;
|
|
|
|
// Take all pointers out, checking they are unique, then replace for destruction.
|
|
while (!uintpool.full ())
|
|
uintset.insert (uintpool.allocate ());
|
|
|
|
tap.expect_eq (uintset.size (), uintpool.capacity (), "extracted maximum elements");
|
|
|
|
for (auto i: uintset)
|
|
uintpool.deallocate (i);
|
|
|
|
tap.expect_eq (uintpool.size (), 0u, "re-inserted maximum elements");
|
|
uintset.clear ();
|
|
|
|
// Do the above one more time to ensure that releasing works right
|
|
while (!uintpool.full ())
|
|
uintset.insert (uintpool.allocate ());
|
|
tap.expect_eq (uintset.size (), uintpool.capacity (), "re-extracted maximum elements");
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Test that every element in a pool points to a unique location.
|
|
void
|
|
check_keep_value (cruft::TAP::logger &tap)
|
|
{
|
|
// Allocate a source pool, and a storage vector.
|
|
cruft::pool<std::size_t > uintpool (64);
|
|
std::vector<std::size_t*> uintvector;
|
|
uintvector.reserve(uintpool.capacity ());
|
|
|
|
// Generate a list of pointers to ints, then fill them with sequential values.
|
|
//
|
|
// Do this as a two step process rather than all at once. This separates
|
|
// the concerns of performing the allocation from whether the allocation
|
|
// points to a safe area. There's a tendency for the pool and vector to be
|
|
// adjacent in memory and overruns in the former impact the latter.
|
|
std::generate_n (
|
|
std::back_inserter (uintvector),
|
|
uintpool.capacity (),
|
|
[&] () { return uintpool.allocate (); }
|
|
);
|
|
|
|
for (std::size_t i = 0; i < uintpool.capacity (); ++i)
|
|
*uintvector[i] = i;
|
|
|
|
CHECK_EQ (uintvector.size (), uintpool.capacity ());
|
|
|
|
// Ensure they're all still present
|
|
std::vector<bool> present(uintpool.capacity (), false);
|
|
for (auto i = uintvector.begin (); i != uintvector.end (); ++i) {
|
|
CHECK (**i < uintpool.capacity ());
|
|
CHECK (present[**i] != true);
|
|
|
|
present[**i] = true;
|
|
}
|
|
|
|
// All must have been marked as present...
|
|
tap.expect (std::find (present.begin (), present.end (), false) == present.end (), "values retained");
|
|
|
|
// Release all back into the pool for destruction
|
|
//for (auto i = uintvector.begin (); i != uintvector.end (); ++i)
|
|
// uintpool.release (*i);
|
|
//uintvector.clear ();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
check_keep_variadic_value (cruft::TAP::logger &tap)
|
|
{
|
|
struct foo_t {
|
|
foo_t (uint64_t _a, uint64_t _b, uint64_t _c, uint64_t _d):
|
|
a (_a),
|
|
b (_b),
|
|
c (_c),
|
|
d (_d)
|
|
{ ; }
|
|
|
|
uint64_t a, b, c, d;
|
|
};
|
|
|
|
cruft::pool<foo_t> alloc (512);
|
|
|
|
bool success = true;
|
|
|
|
for (uint64_t a = 0; a < 8; ++a)
|
|
for (uint64_t b = 0; b < 8; ++b)
|
|
for (uint64_t c = 0; c < 8; ++c) {
|
|
uint64_t d = (a << 24) | (b << 16) | (c << 8);
|
|
auto ptr = alloc.construct (a, b, c, d);
|
|
|
|
if (ptr->a != a || ptr->b != b || ptr->c != c || ptr->d != d) {
|
|
success = false;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
done:
|
|
tap.expect (success, "variadiac construction retains values");
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
check_size_queries (cruft::TAP::logger &tap)
|
|
{
|
|
cruft::pool<int> data (8);
|
|
|
|
tap.expect_eq (data.size (), 0u, "initial size is zero");
|
|
tap.expect (data.empty (), "initial object is empty");
|
|
|
|
auto first = data.allocate ();
|
|
tap.expect_eq (data.size (), 1u, "1 allocation has size of 1");
|
|
tap.expect (!data.empty (), "1 allocation is not empty");
|
|
|
|
data.deallocate (first);
|
|
tap.expect (data.empty (), "full deallocation is empty");
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static void
|
|
check_destructors (cruft::TAP::logger &tap)
|
|
{
|
|
struct counter {
|
|
counter (int *_target): target (_target) { ; }
|
|
|
|
counter (counter const &) = delete;
|
|
counter& operator= (counter const &) = delete;
|
|
|
|
counter (counter &&) = delete;
|
|
counter& operator= (counter &&) = delete;
|
|
|
|
~counter () { if (target) ++*target; }
|
|
|
|
int *target;
|
|
};
|
|
|
|
int count = 0;
|
|
int expected = 0;
|
|
|
|
{
|
|
cruft::pool<counter> data (8);
|
|
auto *first = data.construct (&count);
|
|
auto const *second = data.construct (&count);
|
|
(void)second;
|
|
|
|
CHECK_EQ (count, 0);
|
|
|
|
data.destroy (first);
|
|
++expected;
|
|
tap.expect_eq (count, expected, "destructors run on destroy");
|
|
}
|
|
|
|
expected++;
|
|
tap.expect_eq (count, expected, "single destructor run on pool destructor");
|
|
|
|
// Make an pool without allocations and destroy it. This isn't reported
|
|
// via TAP, but is used as a sanity check that the destructor doesn't run
|
|
// into infinite loops or other such problems.
|
|
{
|
|
cruft::pool<counter> data (8);
|
|
}
|
|
|
|
// Destroy a full pool
|
|
{
|
|
cruft::pool<counter> data (8);
|
|
for (int i = 0; i < 8; ++i) {
|
|
data.construct (&count);
|
|
++expected;
|
|
}
|
|
}
|
|
|
|
tap.expect_eq (count, expected, "prestine full pool destructor triggers all data destructors");
|
|
|
|
{
|
|
cruft::pool<counter> data (8);
|
|
counter* items[8];
|
|
for (int i = 0; i < 8; ++i) {
|
|
items[i] = data.construct (&count);
|
|
++expected;
|
|
}
|
|
|
|
for (int round = 0; round < 128; ++round) {
|
|
auto idx = cruft::random::uniform (std::size (items) - 1);
|
|
data.destroy (items[idx]);
|
|
items[idx] = data.construct (&count);
|
|
++expected;
|
|
}
|
|
}
|
|
|
|
tap.expect_eq (count, expected, "randomised full pool destructor triggers all data destructors");
|
|
|
|
cruft::pool<int> data (100*1024);
|
|
(void)data;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int
|
|
main (int, char **)
|
|
{
|
|
return cruft::TAP::logger::run ([] (auto &tap) {
|
|
check_single (tap);
|
|
check_unique_ptr (tap);
|
|
check_keep_value (tap);
|
|
check_keep_variadic_value (tap);
|
|
check_size_queries (tap);
|
|
check_destructors (tap);
|
|
});
|
|
}
|