/** @file DArenaVector.test.cpp * * @author Roland Conybeare, Jan 2026 **/ #include "DArenaVector.hpp" #include 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 arenavec = DArenaVector::map(cfg); REQUIRE(arenavec.empty()); } TEST_CASE("DArenaVector-push_back-rvalue", "[arena][DArenaVector]") { ArenaConfig cfg { .name_ = "testarena", .size_ = 4096 }; DArenaVector vec = DArenaVector::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 vec = DArenaVector::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 vec = DArenaVector::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 vec = DArenaVector::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 vec = DArenaVector::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(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 vec = DArenaVector::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 vec = DArenaVector::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 vec = DArenaVector::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 vec = DArenaVector::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 vec = DArenaVector::map(cfg); vec.push_back(1.0); vec.push_back(2.0); vec.push_back(3.0); const DArenaVector & 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 vec = DArenaVector::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 vec = DArenaVector::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 vec1 = DArenaVector::map(cfg1); DArenaVector vec2 = DArenaVector::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 vec = DArenaVector::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 & 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 vec1 = DArenaVector::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 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 vec = DArenaVector::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 vec = DArenaVector::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 vec = DArenaVector::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 */