#include #include #include #include #include #include //----------------------------------------------------------------------------- void check_single (cruft::TAP::logger &tap) { // Ensure a single element doesn't break the circular linked list cruft::pool 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 uintpool (element_count); std::set 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 uintpool (64); std::vector 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 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 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 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 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 data (8); } // Destroy a full pool { cruft::pool 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 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 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); }); }