539 lines
16 KiB
C++
539 lines
16 KiB
C++
/** @file DArenaVector.test.cpp
|
|
*
|
|
* @author Roland Conybeare, Jan 2026
|
|
**/
|
|
|
|
#include "DArenaVector.hpp"
|
|
#include <catch2/catch.hpp>
|
|
|
|
namespace xo {
|
|
using xo::mm::DArenaVector;
|
|
using xo::mm::ArenaConfig;
|
|
using std::byte;
|
|
|
|
namespace ut {
|
|
TEST_CASE("DArenaVector-tiny", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 1 };
|
|
DArenaVector<double> arenavec = DArenaVector<double>::map(cfg);
|
|
|
|
REQUIRE(arenavec.empty());
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-push_back-rvalue", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
REQUIRE(vec.empty());
|
|
REQUIRE(vec.size() == 0);
|
|
|
|
vec.push_back(1.5);
|
|
|
|
REQUIRE(!vec.empty());
|
|
REQUIRE(vec.size() == 1);
|
|
REQUIRE(vec[0] == 1.5);
|
|
|
|
vec.push_back(2.5);
|
|
vec.push_back(3.5);
|
|
|
|
REQUIRE(vec.size() == 3);
|
|
REQUIRE(vec[0] == 1.5);
|
|
REQUIRE(vec[1] == 2.5);
|
|
REQUIRE(vec[2] == 3.5);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-push_back-lvalue", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
double a = 10.0;
|
|
double b = 20.0;
|
|
double c = 30.0;
|
|
|
|
vec.push_back(a);
|
|
|
|
REQUIRE(vec.size() == 1);
|
|
REQUIRE(vec[0] == 10.0);
|
|
|
|
vec.push_back(b);
|
|
vec.push_back(c);
|
|
|
|
REQUIRE(vec.size() == 3);
|
|
REQUIRE(vec[0] == 10.0);
|
|
REQUIRE(vec[1] == 20.0);
|
|
REQUIRE(vec[2] == 30.0);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-at-valid", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
vec.push_back(100.0);
|
|
vec.push_back(200.0);
|
|
vec.push_back(300.0);
|
|
|
|
REQUIRE(vec.at(0) == 100.0);
|
|
REQUIRE(vec.at(1) == 200.0);
|
|
REQUIRE(vec.at(2) == 300.0);
|
|
|
|
// test mutability via at()
|
|
vec.at(1) = 250.0;
|
|
REQUIRE(vec.at(1) == 250.0);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-at-throws", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
// empty vector - any index is invalid
|
|
REQUIRE_THROWS_AS(vec.at(0), std::out_of_range);
|
|
|
|
vec.push_back(1.0);
|
|
vec.push_back(2.0);
|
|
|
|
// valid indices work
|
|
REQUIRE_NOTHROW(vec.at(0));
|
|
REQUIRE_NOTHROW(vec.at(1));
|
|
|
|
// index == size is invalid
|
|
REQUIRE_THROWS_AS(vec.at(2), std::out_of_range);
|
|
|
|
// index > size is invalid
|
|
REQUIRE_THROWS_AS(vec.at(100), std::out_of_range);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-resize-expand", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
REQUIRE(vec.size() == 0);
|
|
|
|
// resize from 0 to 5
|
|
vec.resize(5);
|
|
REQUIRE(vec.size() == 5);
|
|
|
|
// can write to all indices
|
|
for (size_t i = 0; i < 5; ++i) {
|
|
vec[i] = static_cast<double>(i * 10);
|
|
}
|
|
|
|
REQUIRE(vec[0] == 0.0);
|
|
REQUIRE(vec[1] == 10.0);
|
|
REQUIRE(vec[2] == 20.0);
|
|
REQUIRE(vec[3] == 30.0);
|
|
REQUIRE(vec[4] == 40.0);
|
|
|
|
// resize to larger
|
|
vec.resize(8);
|
|
REQUIRE(vec.size() == 8);
|
|
|
|
// original values preserved
|
|
REQUIRE(vec[0] == 0.0);
|
|
REQUIRE(vec[1] == 10.0);
|
|
REQUIRE(vec[4] == 40.0);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-resize-shrink", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
vec.push_back(1.0);
|
|
vec.push_back(2.0);
|
|
vec.push_back(3.0);
|
|
vec.push_back(4.0);
|
|
vec.push_back(5.0);
|
|
|
|
REQUIRE(vec.size() == 5);
|
|
|
|
// shrink to 3
|
|
vec.resize(3);
|
|
REQUIRE(vec.size() == 3);
|
|
|
|
// first 3 elements preserved
|
|
REQUIRE(vec[0] == 1.0);
|
|
REQUIRE(vec[1] == 2.0);
|
|
REQUIRE(vec[2] == 3.0);
|
|
|
|
// index 3 now out of bounds
|
|
REQUIRE_THROWS_AS(vec.at(3), std::out_of_range);
|
|
|
|
// shrink to 0
|
|
vec.resize(0);
|
|
REQUIRE(vec.size() == 0);
|
|
REQUIRE(vec.empty());
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-resize-same", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
vec.push_back(10.0);
|
|
vec.push_back(20.0);
|
|
vec.push_back(30.0);
|
|
|
|
REQUIRE(vec.size() == 3);
|
|
|
|
// resize to same size
|
|
vec.resize(3);
|
|
REQUIRE(vec.size() == 3);
|
|
|
|
// values unchanged
|
|
REQUIRE(vec[0] == 10.0);
|
|
REQUIRE(vec[1] == 20.0);
|
|
REQUIRE(vec[2] == 30.0);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-clear", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
vec.push_back(1.0);
|
|
vec.push_back(2.0);
|
|
vec.push_back(3.0);
|
|
|
|
REQUIRE(vec.size() == 3);
|
|
REQUIRE(!vec.empty());
|
|
|
|
vec.clear();
|
|
|
|
REQUIRE(vec.size() == 0);
|
|
REQUIRE(vec.empty());
|
|
|
|
// can still push after clear
|
|
vec.push_back(99.0);
|
|
REQUIRE(vec.size() == 1);
|
|
REQUIRE(vec[0] == 99.0);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-iterators", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
vec.push_back(10.0);
|
|
vec.push_back(20.0);
|
|
vec.push_back(30.0);
|
|
|
|
// begin/end
|
|
REQUIRE(vec.begin() != vec.end());
|
|
REQUIRE(vec.end() - vec.begin() == 3);
|
|
|
|
// iterate with pointer arithmetic
|
|
auto it = vec.begin();
|
|
REQUIRE(*it == 10.0);
|
|
++it;
|
|
REQUIRE(*it == 20.0);
|
|
++it;
|
|
REQUIRE(*it == 30.0);
|
|
++it;
|
|
REQUIRE(it == vec.end());
|
|
|
|
// modify through iterator
|
|
*vec.begin() = 15.0;
|
|
REQUIRE(vec[0] == 15.0);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-const-iterators", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
vec.push_back(1.0);
|
|
vec.push_back(2.0);
|
|
vec.push_back(3.0);
|
|
|
|
const DArenaVector<double> & cvec = vec;
|
|
|
|
REQUIRE(cvec.cbegin() != cvec.cend());
|
|
REQUIRE(cvec.begin() == cvec.cbegin());
|
|
REQUIRE(cvec.end() == cvec.cend());
|
|
|
|
auto it = cvec.cbegin();
|
|
REQUIRE(*it == 1.0);
|
|
++it;
|
|
REQUIRE(*it == 2.0);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-range-for", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
vec.push_back(1.0);
|
|
vec.push_back(2.0);
|
|
vec.push_back(3.0);
|
|
|
|
// read via range-for
|
|
double sum = 0.0;
|
|
for (double x : vec) {
|
|
sum += x;
|
|
}
|
|
REQUIRE(sum == 6.0);
|
|
|
|
// modify via range-for
|
|
for (double & x : vec) {
|
|
x *= 2.0;
|
|
}
|
|
REQUIRE(vec[0] == 2.0);
|
|
REQUIRE(vec[1] == 4.0);
|
|
REQUIRE(vec[2] == 6.0);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-reserve", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
REQUIRE(vec.size() == 0);
|
|
REQUIRE(vec.capacity() > 0);
|
|
|
|
size_t initial_capacity = vec.capacity();
|
|
|
|
// reserve doesn't change size
|
|
vec.reserve(100);
|
|
REQUIRE(vec.size() == 0);
|
|
REQUIRE(vec.capacity() >= 100);
|
|
|
|
// add some elements
|
|
vec.push_back(1.0);
|
|
vec.push_back(2.0);
|
|
vec.push_back(3.0);
|
|
|
|
REQUIRE(vec.size() == 3);
|
|
size_t cap_after_push = vec.capacity();
|
|
|
|
// reserve more space
|
|
vec.reserve(200);
|
|
REQUIRE(vec.size() == 3);
|
|
REQUIRE(vec.capacity() >= 200);
|
|
|
|
// values still intact
|
|
REQUIRE(vec[0] == 1.0);
|
|
REQUIRE(vec[1] == 2.0);
|
|
REQUIRE(vec[2] == 3.0);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-swap", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg1 { .name_ = "testarena1",
|
|
.size_ = 4096 };
|
|
ArenaConfig cfg2 { .name_ = "testarena2",
|
|
.size_ = 4096 };
|
|
|
|
DArenaVector<double> vec1 = DArenaVector<double>::map(cfg1);
|
|
DArenaVector<double> vec2 = DArenaVector<double>::map(cfg2);
|
|
|
|
vec1.push_back(1.0);
|
|
vec1.push_back(2.0);
|
|
|
|
vec2.push_back(10.0);
|
|
vec2.push_back(20.0);
|
|
vec2.push_back(30.0);
|
|
|
|
REQUIRE(vec1.size() == 2);
|
|
REQUIRE(vec2.size() == 3);
|
|
|
|
vec1.swap(vec2);
|
|
|
|
// sizes swapped
|
|
REQUIRE(vec1.size() == 3);
|
|
REQUIRE(vec2.size() == 2);
|
|
|
|
// contents swapped
|
|
REQUIRE(vec1[0] == 10.0);
|
|
REQUIRE(vec1[1] == 20.0);
|
|
REQUIRE(vec1[2] == 30.0);
|
|
|
|
REQUIRE(vec2[0] == 1.0);
|
|
REQUIRE(vec2[1] == 2.0);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-data", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec = DArenaVector<double>::map(cfg);
|
|
|
|
vec.push_back(1.0);
|
|
vec.push_back(2.0);
|
|
vec.push_back(3.0);
|
|
|
|
double * ptr = vec.data();
|
|
|
|
// data() points to first element
|
|
REQUIRE(ptr == &vec[0]);
|
|
|
|
// can read via pointer
|
|
REQUIRE(ptr[0] == 1.0);
|
|
REQUIRE(ptr[1] == 2.0);
|
|
REQUIRE(ptr[2] == 3.0);
|
|
|
|
// can write via pointer
|
|
ptr[1] = 99.0;
|
|
REQUIRE(vec[1] == 99.0);
|
|
|
|
// const version
|
|
const DArenaVector<double> & cvec = vec;
|
|
const double * cptr = cvec.data();
|
|
REQUIRE(cptr[0] == 1.0);
|
|
REQUIRE(cptr[1] == 99.0);
|
|
REQUIRE(cptr[2] == 3.0);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-move-ctor", "[arena][DArenaVector]")
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<double> vec1 = DArenaVector<double>::map(cfg);
|
|
|
|
vec1.push_back(10.0);
|
|
vec1.push_back(20.0);
|
|
vec1.push_back(30.0);
|
|
|
|
double * original_data = vec1.data();
|
|
size_t original_size = vec1.size();
|
|
|
|
// move construct vec2 from vec1
|
|
DArenaVector<double> vec2(std::move(vec1));
|
|
|
|
// vec2 has the data
|
|
REQUIRE(vec2.size() == original_size);
|
|
REQUIRE(vec2.data() == original_data);
|
|
REQUIRE(vec2[0] == 10.0);
|
|
REQUIRE(vec2[1] == 20.0);
|
|
REQUIRE(vec2[2] == 30.0);
|
|
|
|
// vec1 is in valid but moved-from state
|
|
REQUIRE(vec1.size() == 0);
|
|
REQUIRE(vec1.empty());
|
|
}
|
|
|
|
// Helper class to track ctor/dtor calls
|
|
struct LifetimeTracker {
|
|
static int ctor_count;
|
|
static int dtor_count;
|
|
|
|
static void reset() {
|
|
ctor_count = 0;
|
|
dtor_count = 0;
|
|
}
|
|
|
|
int value;
|
|
|
|
LifetimeTracker() : value{0} { ++ctor_count; }
|
|
LifetimeTracker(int v) : value{v} { ++ctor_count; }
|
|
LifetimeTracker(const LifetimeTracker & other) : value{other.value} { ++ctor_count; }
|
|
LifetimeTracker(LifetimeTracker && other) : value{other.value} { ++ctor_count; other.value = 0; }
|
|
~LifetimeTracker() { ++dtor_count; }
|
|
|
|
LifetimeTracker & operator=(const LifetimeTracker &) = default;
|
|
LifetimeTracker & operator=(LifetimeTracker &&) = default;
|
|
};
|
|
|
|
int LifetimeTracker::ctor_count = 0;
|
|
int LifetimeTracker::dtor_count = 0;
|
|
|
|
TEST_CASE("DArenaVector-nontrivial-dtor", "[arena][DArenaVector]")
|
|
{
|
|
LifetimeTracker::reset();
|
|
|
|
{
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<LifetimeTracker> vec = DArenaVector<LifetimeTracker>::map(cfg);
|
|
|
|
vec.push_back(LifetimeTracker{1});
|
|
vec.push_back(LifetimeTracker{2});
|
|
vec.push_back(LifetimeTracker{3});
|
|
|
|
// 3 temp objects created, 3 moved into vector
|
|
// temps destroyed after push_back
|
|
REQUIRE(vec.size() == 3);
|
|
REQUIRE(LifetimeTracker::ctor_count == 6); // 3 temps + 3 moves
|
|
REQUIRE(LifetimeTracker::dtor_count == 3); // 3 temps destroyed
|
|
|
|
// verify values
|
|
REQUIRE(vec[0].value == 1);
|
|
REQUIRE(vec[1].value == 2);
|
|
REQUIRE(vec[2].value == 3);
|
|
}
|
|
// vec destroyed, should call dtor for all 3 elements
|
|
REQUIRE(LifetimeTracker::dtor_count == 6);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-nontrivial-resize-shrink", "[arena][DArenaVector]")
|
|
{
|
|
LifetimeTracker::reset();
|
|
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<LifetimeTracker> vec = DArenaVector<LifetimeTracker>::map(cfg);
|
|
|
|
vec.push_back(LifetimeTracker{1});
|
|
vec.push_back(LifetimeTracker{2});
|
|
vec.push_back(LifetimeTracker{3});
|
|
vec.push_back(LifetimeTracker{4});
|
|
vec.push_back(LifetimeTracker{5});
|
|
|
|
REQUIRE(vec.size() == 5);
|
|
int dtors_before_shrink = LifetimeTracker::dtor_count;
|
|
|
|
// shrink from 5 to 2
|
|
vec.resize(2);
|
|
|
|
REQUIRE(vec.size() == 2);
|
|
// should have called dtor for 3 elements (indices 2,3,4)
|
|
REQUIRE(LifetimeTracker::dtor_count == dtors_before_shrink + 3);
|
|
|
|
// remaining elements intact
|
|
REQUIRE(vec[0].value == 1);
|
|
REQUIRE(vec[1].value == 2);
|
|
}
|
|
|
|
TEST_CASE("DArenaVector-nontrivial-clear", "[arena][DArenaVector]")
|
|
{
|
|
LifetimeTracker::reset();
|
|
|
|
ArenaConfig cfg { .name_ = "testarena",
|
|
.size_ = 4096 };
|
|
DArenaVector<LifetimeTracker> vec = DArenaVector<LifetimeTracker>::map(cfg);
|
|
|
|
vec.push_back(LifetimeTracker{1});
|
|
vec.push_back(LifetimeTracker{2});
|
|
vec.push_back(LifetimeTracker{3});
|
|
|
|
REQUIRE(vec.size() == 3);
|
|
int dtors_before_clear = LifetimeTracker::dtor_count;
|
|
|
|
vec.clear();
|
|
|
|
REQUIRE(vec.size() == 0);
|
|
REQUIRE(vec.empty());
|
|
// should have called dtor for all 3 elements
|
|
REQUIRE(LifetimeTracker::dtor_count == dtors_before_clear + 3);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* end DArenaVector.test.cpp */
|