libcruft-util/test/pool.cpp

241 lines
6.9 KiB
C++

#include "../pool.hpp"
#include "../tap.hpp"
#include "../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);
});
}