Add tests and reformat

This commit is contained in:
Georg Hagen
2025-05-13 20:15:13 +02:00
parent 69ecaf7725
commit 3fd31f92ae
2 changed files with 362 additions and 276 deletions

View File

@@ -15,7 +15,8 @@
using namespace OpenVulkano; using namespace OpenVulkano;
// Helper to track construction/destruction // Helper to track construction/destruction
struct Tracked { struct Tracked
{
static inline int ctorCount = 0; static inline int ctorCount = 0;
static inline int dtorCount = 0; static inline int dtorCount = 0;
static inline int moveCount = 0; static inline int moveCount = 0;
@@ -23,44 +24,62 @@ struct Tracked {
int value; int value;
Tracked(int v = 0) : value(v) { ++ctorCount; } Tracked(int v = 0) : value(v) { ++ctorCount; }
Tracked(const Tracked& other) : value(other.value) { ++ctorCount; ++copyCount; }
Tracked(Tracked&& other) noexcept : value(other.value) { ++ctorCount; ++moveCount; } Tracked(const Tracked& other) : value(other.value)
{
++ctorCount;
++copyCount;
}
Tracked(Tracked&& other) noexcept: value(other.value)
{
++ctorCount;
++moveCount;
}
~Tracked() { ++dtorCount; } ~Tracked() { ++dtorCount; }
Tracked& operator=(const Tracked& other) { Tracked& operator=(const Tracked& other)
{
value = other.value; value = other.value;
++copyCount; ++copyCount;
return *this; return *this;
} }
Tracked& operator=(Tracked&& other) noexcept { Tracked& operator=(Tracked&& other) noexcept
{
value = other.value; value = other.value;
++moveCount; ++moveCount;
return *this; return *this;
} }
friend bool operator==(const Tracked& lhs, const Tracked& rhs) { friend bool operator==(const Tracked& lhs, const Tracked& rhs)
{
return lhs.value == rhs.value; return lhs.value == rhs.value;
} }
}; };
void reset_tracking() { void reset_tracking()
{
Tracked::ctorCount = 0; Tracked::ctorCount = 0;
Tracked::dtorCount = 0; Tracked::dtorCount = 0;
Tracked::moveCount = 0; Tracked::moveCount = 0;
Tracked::copyCount = 0; Tracked::copyCount = 0;
} }
TEST_CASE("RingBuffer Basic Construction", "[RingBuffer]") { TEST_CASE("RingBuffer Basic Construction", "[RingBuffer]")
SECTION("Static buffer construction") { {
SECTION("Static buffer construction")
{
RingBuffer<int, 5> buf; RingBuffer<int, 5> buf;
REQUIRE(buf.Capacity() == 5); REQUIRE(buf.Capacity() == 5);
REQUIRE(buf.IsEmpty()); REQUIRE(buf.IsEmpty());
REQUIRE(buf.Count() == 0); REQUIRE(buf.Count() == 0);
REQUIRE(buf.HasFree()); REQUIRE(buf.HasFree());
} }
SECTION("Dynamic buffer construction") { SECTION("Dynamic buffer construction")
{
RingBuffer<int> buf(10); RingBuffer<int> buf(10);
REQUIRE(buf.Capacity() == 10); REQUIRE(buf.Capacity() == 10);
REQUIRE(buf.IsEmpty()); REQUIRE(buf.IsEmpty());
@@ -69,7 +88,8 @@ TEST_CASE("RingBuffer Basic Construction", "[RingBuffer]") {
} }
} }
TEST_CASE("RingBuffer Tracking Destruction", "[RingBuffer][Tracked]") { TEST_CASE("RingBuffer Tracking Destruction", "[RingBuffer][Tracked]")
{
reset_tracking(); reset_tracking();
{ {
@@ -83,38 +103,43 @@ TEST_CASE("RingBuffer Tracking Destruction", "[RingBuffer][Tracked]") {
REQUIRE(Tracked::dtorCount == Tracked::ctorCount); REQUIRE(Tracked::dtorCount == Tracked::ctorCount);
} }
TEST_CASE("RingBuffer Move and Copy Semantics", "[RingBuffer][Tracked]") { TEST_CASE("RingBuffer Move and Copy Semantics", "[RingBuffer][Tracked]")
{
reset_tracking(); reset_tracking();
{ {
RingBuffer<Tracked, 3> buf; RingBuffer<Tracked, 3> buf;
SECTION("Push with move") { SECTION("Push with move")
{
Tracked t(42); Tracked t(42);
buf.Push(std::move(t)); buf.Push(std::move(t));
REQUIRE(Tracked::moveCount >= 1); REQUIRE(Tracked::moveCount >= 1);
REQUIRE(buf.Front().value == 42); REQUIRE(buf.Front().value == 42);
} }
SECTION("Push with copy") { SECTION("Push with copy")
{
Tracked t(42); Tracked t(42);
buf.Push(t); buf.Push(t);
REQUIRE(Tracked::copyCount >= 1); REQUIRE(Tracked::copyCount >= 1);
REQUIRE(buf.Front().value == 42); REQUIRE(buf.Front().value == 42);
} }
SECTION("Emplace constructs in-place") { SECTION("Emplace constructs in-place")
{
int initialCtors = Tracked::ctorCount; int initialCtors = Tracked::ctorCount;
buf.Emplace(99); buf.Emplace(99);
REQUIRE(Tracked::ctorCount == initialCtors + 1); REQUIRE(Tracked::ctorCount == initialCtors + 1);
REQUIRE(buf.Front().value == 99); REQUIRE(buf.Front().value == 99);
} }
} }
REQUIRE(Tracked::dtorCount == Tracked::ctorCount); REQUIRE(Tracked::dtorCount == Tracked::ctorCount);
} }
TEST_CASE("RingBuffer Push and Pop Operations", "[RingBuffer]") { TEST_CASE("RingBuffer Push and Pop Operations", "[RingBuffer]")
{
RingBuffer<int, 3> buf; RingBuffer<int, 3> buf;
buf.Push(1); buf.Push(1);
@@ -124,7 +149,8 @@ TEST_CASE("RingBuffer Push and Pop Operations", "[RingBuffer]") {
REQUIRE(buf.Count() == 3); REQUIRE(buf.Count() == 3);
REQUIRE_FALSE(buf.HasFree()); REQUIRE_FALSE(buf.HasFree());
SECTION("PopFront/Back correctness") { SECTION("PopFront/Back correctness")
{
REQUIRE(buf.PopBack() == 1); REQUIRE(buf.PopBack() == 1);
REQUIRE(buf.PopFront() == 3); REQUIRE(buf.PopFront() == 3);
REQUIRE(buf.Count() == 1); REQUIRE(buf.Count() == 1);
@@ -134,34 +160,38 @@ TEST_CASE("RingBuffer Push and Pop Operations", "[RingBuffer]") {
REQUIRE_THROWS_AS(buf.PopBack(), std::underflow_error); REQUIRE_THROWS_AS(buf.PopBack(), std::underflow_error);
} }
SECTION("Clear empties the buffer") { SECTION("Clear empties the buffer")
{
buf.Clear(); buf.Clear();
REQUIRE(buf.IsEmpty()); REQUIRE(buf.IsEmpty());
REQUIRE(buf.Count() == 0); REQUIRE(buf.Count() == 0);
} }
SECTION("Front and Back access") { SECTION("Front and Back access")
{
REQUIRE(buf.Front() == 3); REQUIRE(buf.Front() == 3);
REQUIRE(buf.Back() == 1); REQUIRE(buf.Back() == 1);
buf.PopFront(); buf.PopFront();
REQUIRE(buf.Front() == 2); REQUIRE(buf.Front() == 2);
REQUIRE(buf.Back() == 1); REQUIRE(buf.Back() == 1);
buf.PopBack(); buf.PopBack();
REQUIRE(buf.Front() == 2); REQUIRE(buf.Front() == 2);
REQUIRE(buf.Back() == 2); REQUIRE(buf.Back() == 2);
} }
} }
TEST_CASE("RingBuffer PushNoOverwrite operation", "[RingBuffer]") { TEST_CASE("RingBuffer PushNoOverwrite operation", "[RingBuffer]")
{
RingBuffer<int, 2> buf; RingBuffer<int, 2> buf;
buf.PushNoOverwrite(1); buf.PushNoOverwrite(1);
buf.PushNoOverwrite(2); buf.PushNoOverwrite(2);
REQUIRE_THROWS_AS(buf.PushNoOverwrite(3), std::overflow_error); REQUIRE_THROWS_AS(buf.PushNoOverwrite(3), std::overflow_error);
SECTION("EmplaceNoOverwrite") { SECTION("EmplaceNoOverwrite")
{
RingBuffer<std::string, 2> strBuf; RingBuffer<std::string, 2> strBuf;
strBuf.EmplaceNoOverwrite("first"); strBuf.EmplaceNoOverwrite("first");
strBuf.EmplaceNoOverwrite("second"); strBuf.EmplaceNoOverwrite("second");
@@ -169,18 +199,21 @@ TEST_CASE("RingBuffer PushNoOverwrite operation", "[RingBuffer]") {
} }
} }
TEST_CASE("RingBuffer PushAndOverwrite operation", "[RingBuffer]") { TEST_CASE("RingBuffer PushAndOverwrite operation", "[RingBuffer]")
{
RingBuffer<int, 2> buf; RingBuffer<int, 2> buf;
SECTION("Cannot use PushAndOverwrite when buffer not full") { SECTION("Cannot use PushAndOverwrite when buffer not full")
{
buf.Push(1); buf.Push(1);
REQUIRE_THROWS_AS(buf.PushAndOverwrite(5), std::runtime_error); REQUIRE_THROWS_AS(buf.PushAndOverwrite(5), std::runtime_error);
} }
SECTION("PushAndOverwrite returns overwritten value") { SECTION("PushAndOverwrite returns overwritten value")
{
buf.Fill(42); buf.Fill(42);
REQUIRE(buf.Count() == 2); REQUIRE(buf.Count() == 2);
int overwritten = buf.PushAndOverwrite(99); int overwritten = buf.PushAndOverwrite(99);
REQUIRE(overwritten == 42); REQUIRE(overwritten == 42);
REQUIRE(buf.Front() == 99); REQUIRE(buf.Front() == 99);
@@ -188,12 +221,14 @@ TEST_CASE("RingBuffer PushAndOverwrite operation", "[RingBuffer]") {
} }
} }
TEST_CASE("PushBack and PushFront Overwrite Logic", "[RingBuffer]") { TEST_CASE("PushBack and PushFront Overwrite Logic", "[RingBuffer]")
{
RingBuffer<int, 2> buf; RingBuffer<int, 2> buf;
buf.Push(1); buf.Push(1);
buf.Push(2); buf.Push(2);
SECTION("PushFront overwrites correctly") { SECTION("PushFront overwrites correctly")
{
auto overwrittenFront = buf.PushFront(3); auto overwrittenFront = buf.PushFront(3);
REQUIRE(overwrittenFront.has_value()); REQUIRE(overwrittenFront.has_value());
REQUIRE(overwrittenFront.value() == 1); REQUIRE(overwrittenFront.value() == 1);
@@ -201,29 +236,32 @@ TEST_CASE("PushBack and PushFront Overwrite Logic", "[RingBuffer]") {
REQUIRE(buf.Back() == 2); REQUIRE(buf.Back() == 2);
} }
SECTION("PushBack overwrites correctly") { SECTION("PushBack overwrites correctly")
{
auto overwrittenBack = buf.PushBack(4); auto overwrittenBack = buf.PushBack(4);
REQUIRE(overwrittenBack.has_value()); REQUIRE(overwrittenBack.has_value());
REQUIRE(overwrittenBack.value() == 1); REQUIRE(overwrittenBack.value() == 1);
REQUIRE(buf.Front() == 2); REQUIRE(buf.Front() == 2);
REQUIRE(buf.Back() == 4); REQUIRE(buf.Back() == 4);
} }
SECTION("PushFront with available space doesn't overwrite") { SECTION("PushFront with available space doesn't overwrite")
{
RingBuffer<int, 3> buf2; RingBuffer<int, 3> buf2;
buf2.Push(10); buf2.Push(10);
auto result = buf2.PushFront(20); auto result = buf2.PushFront(20);
REQUIRE_FALSE(result.has_value()); REQUIRE_FALSE(result.has_value());
REQUIRE(buf2.Count() == 2); REQUIRE(buf2.Count() == 2);
REQUIRE(buf2.Front() == 20); REQUIRE(buf2.Front() == 20);
REQUIRE(buf2.Back() == 10); REQUIRE(buf2.Back() == 10);
} }
SECTION("PushBack with available space doesn't overwrite") { SECTION("PushBack with available space doesn't overwrite")
{
RingBuffer<int, 3> buf2; RingBuffer<int, 3> buf2;
buf2.Push(10); buf2.Push(10);
auto result = buf2.PushBack(20); auto result = buf2.PushBack(20);
REQUIRE_FALSE(result.has_value()); REQUIRE_FALSE(result.has_value());
REQUIRE(buf2.Count() == 2); REQUIRE(buf2.Count() == 2);
@@ -232,7 +270,8 @@ TEST_CASE("PushBack and PushFront Overwrite Logic", "[RingBuffer]") {
} }
} }
TEST_CASE("at() bounds checking", "[RingBuffer]") { TEST_CASE("at() bounds checking", "[RingBuffer]")
{
RingBuffer<int, 3> buf; RingBuffer<int, 3> buf;
buf.Push(100); buf.Push(100);
buf.Push(200); buf.Push(200);
@@ -240,8 +279,9 @@ TEST_CASE("at() bounds checking", "[RingBuffer]") {
REQUIRE(buf.at(0) == 100); REQUIRE(buf.at(0) == 100);
REQUIRE(buf.at(1) == 200); REQUIRE(buf.at(1) == 200);
REQUIRE_THROWS_AS(buf.at(2), std::range_error); REQUIRE_THROWS_AS(buf.at(2), std::range_error);
SECTION("const at() works correctly") { SECTION("const at() works correctly")
{
const auto& constBuf = buf; const auto& constBuf = buf;
REQUIRE(constBuf.at(0) == 100); REQUIRE(constBuf.at(0) == 100);
REQUIRE(constBuf.at(1) == 200); REQUIRE(constBuf.at(1) == 200);
@@ -249,7 +289,8 @@ TEST_CASE("at() bounds checking", "[RingBuffer]") {
} }
} }
TEST_CASE("Index-based Access and Wraparound", "[RingBuffer]") { TEST_CASE("Index-based Access and Wraparound", "[RingBuffer]")
{
RingBuffer<int, 3> buf; RingBuffer<int, 3> buf;
buf.Push(1); buf.Push(1);
@@ -273,46 +314,52 @@ TEST_CASE("Index-based Access and Wraparound", "[RingBuffer]") {
REQUIRE(buf[0] == 4); REQUIRE(buf[0] == 4);
REQUIRE(buf[1] == 5); REQUIRE(buf[1] == 5);
REQUIRE(buf[2] == 6); REQUIRE(buf[2] == 6);
SECTION("Wraparound with power-of-2 size") { SECTION("Wraparound with power-of-2 size")
{
RingBuffer<int, 4> powBuf; // Power of 2 size for different code path RingBuffer<int, 4> powBuf; // Power of 2 size for different code path
for (int i = 1; i <= 4; i++) { for (int i = 1; i <= 4; i++)
{
powBuf.Push(i); powBuf.Push(i);
} }
REQUIRE(powBuf[0] == 1); REQUIRE(powBuf[0] == 1);
REQUIRE(powBuf[3] == 4); REQUIRE(powBuf[3] == 4);
powBuf.PopBack(); powBuf.PopBack();
powBuf.Push(5); powBuf.Push(5);
REQUIRE(powBuf[0] == 2); REQUIRE(powBuf[0] == 2);
REQUIRE(powBuf[3] == 5); REQUIRE(powBuf[3] == 5);
} }
} }
TEST_CASE("Fill operation", "[RingBuffer]") { TEST_CASE("Fill operation", "[RingBuffer]")
{
RingBuffer<int, 5> buf; RingBuffer<int, 5> buf;
SECTION("Fill empty buffer") { SECTION("Fill empty buffer")
{
buf.Fill(42); buf.Fill(42);
REQUIRE(buf.Count() == 5); REQUIRE(buf.Count() == 5);
REQUIRE_FALSE(buf.HasFree()); REQUIRE_FALSE(buf.HasFree());
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++)
{
REQUIRE(buf[i] == 42); REQUIRE(buf[i] == 42);
} }
} }
SECTION("Fill partially full buffer") { SECTION("Fill partially full buffer")
{
buf.Push(1); buf.Push(1);
buf.Push(2); buf.Push(2);
buf.Fill(99); buf.Fill(99);
REQUIRE(buf.Count() == 5); REQUIRE(buf.Count() == 5);
REQUIRE_FALSE(buf.HasFree()); REQUIRE_FALSE(buf.HasFree());
REQUIRE(buf[0] == 1); REQUIRE(buf[0] == 1);
REQUIRE(buf[1] == 2); REQUIRE(buf[1] == 2);
REQUIRE(buf[2] == 99); REQUIRE(buf[2] == 99);
@@ -321,7 +368,8 @@ TEST_CASE("Fill operation", "[RingBuffer]") {
} }
} }
TEST_CASE("Emplace and EmplaceBack/Front work correctly", "[RingBuffer]") { TEST_CASE("Emplace and EmplaceBack/Front work correctly", "[RingBuffer]")
{
RingBuffer<std::string, 2> buf; RingBuffer<std::string, 2> buf;
buf.Emplace("first"); buf.Emplace("first");
@@ -335,8 +383,9 @@ TEST_CASE("Emplace and EmplaceBack/Front work correctly", "[RingBuffer]") {
REQUIRE(overwritten.has_value()); REQUIRE(overwritten.has_value());
REQUIRE(overwritten.value() == "second"); REQUIRE(overwritten.value() == "second");
REQUIRE(buf.Front() == "new"); REQUIRE(buf.Front() == "new");
SECTION("EmplaceNoOverwrite throws when full") { SECTION("EmplaceNoOverwrite throws when full")
{
RingBuffer<std::string, 2> buf2; RingBuffer<std::string, 2> buf2;
buf2.EmplaceNoOverwrite("one"); buf2.EmplaceNoOverwrite("one");
buf2.EmplaceNoOverwrite("two"); buf2.EmplaceNoOverwrite("two");
@@ -344,26 +393,30 @@ TEST_CASE("Emplace and EmplaceBack/Front work correctly", "[RingBuffer]") {
} }
} }
TEST_CASE("Iterators forward and reverse", "[RingBuffer]") { TEST_CASE("Iterators forward and reverse", "[RingBuffer]")
{
RingBuffer<int, 4> buf; RingBuffer<int, 4> buf;
buf.Push(10); buf.Push(10);
buf.Push(20); buf.Push(20);
buf.Push(30); buf.Push(30);
SECTION("Forward iteration") { SECTION("Forward iteration")
{
std::vector<int> forward; std::vector<int> forward;
for (int val : buf) forward.push_back(val); for (int val: buf) forward.push_back(val);
REQUIRE(forward == std::vector<int>{10, 20, 30}); REQUIRE(forward == std::vector<int> {10, 20, 30});
} }
SECTION("Reverse iteration") { SECTION("Reverse iteration")
{
std::vector<int> reverse; std::vector<int> reverse;
for (auto it = buf.rbegin(); it != buf.rend(); ++it) for (auto it = buf.rbegin(); it != buf.rend(); ++it)
reverse.push_back(*it); reverse.push_back(*it);
REQUIRE(reverse == std::vector<int>{30, 20, 10}); REQUIRE(reverse == std::vector<int> {30, 20, 10});
} }
SECTION("Iterator decrement") { SECTION("Iterator decrement")
{
auto it = buf.end(); auto it = buf.end();
--it; --it;
REQUIRE(*it == 30); REQUIRE(*it == 30);
@@ -372,8 +425,9 @@ TEST_CASE("Iterators forward and reverse", "[RingBuffer]") {
--it; --it;
REQUIRE(*it == 10); REQUIRE(*it == 10);
} }
SECTION("Reverse iterator decrement") { SECTION("Reverse iterator decrement")
{
auto it = buf.rend(); auto it = buf.rend();
--it; --it;
REQUIRE(*it == 10); REQUIRE(*it == 10);
@@ -382,54 +436,63 @@ TEST_CASE("Iterators forward and reverse", "[RingBuffer]") {
--it; --it;
REQUIRE(*it == 30); REQUIRE(*it == 30);
} }
SECTION("Iterator post-increment/decrement") { SECTION("Iterator post-increment/decrement")
{
auto it = buf.begin(); auto it = buf.begin();
REQUIRE(*(it++) == 10); REQUIRE(*(it++) == 10);
REQUIRE(*it == 20); REQUIRE(*it == 20);
auto it2 = --buf.end(); auto it2 = --buf.end();
REQUIRE(*(it2--) == 30); REQUIRE(*(it2--) == 30);
REQUIRE(*it2 == 20); REQUIRE(*it2 == 20);
} }
SECTION("Empty buffer iteration") { SECTION("Empty buffer iteration")
{
RingBuffer<int, 3> emptyBuf; RingBuffer<int, 3> emptyBuf;
REQUIRE(emptyBuf.begin() == emptyBuf.end()); REQUIRE(emptyBuf.begin() == emptyBuf.end());
REQUIRE(emptyBuf.rbegin() == emptyBuf.rend()); REQUIRE(emptyBuf.rbegin() == emptyBuf.rend());
} }
} }
TEST_CASE("Const correctness in iterators", "[RingBuffer][Const]") { TEST_CASE("Const correctness in iterators", "[RingBuffer][Const]")
{
RingBuffer<int, 3> buf; RingBuffer<int, 3> buf;
buf.Push(5); buf.Push(5);
buf.Push(6); buf.Push(6);
const auto& constBuf = buf; const auto& constBuf = buf;
SECTION("const_iterator usage") { SECTION("const_iterator usage")
{
std::ostringstream oss; std::ostringstream oss;
for (auto it = constBuf.cbegin(); it != constBuf.cend(); ++it) { for (auto it = constBuf.cbegin(); it != constBuf.cend(); ++it)
{
oss << *it << " "; oss << *it << " ";
} }
REQUIRE(oss.str() == "5 6 "); REQUIRE(oss.str() == "5 6 ");
} }
SECTION("const rbegin/rend usage") { SECTION("const rbegin/rend usage")
{
std::vector<int> values; std::vector<int> values;
for (auto it = constBuf.crbegin(); it != constBuf.crend(); ++it) { for (auto it = constBuf.crbegin(); it != constBuf.crend(); ++it)
{
values.push_back(*it); values.push_back(*it);
} }
REQUIRE(values == std::vector<int>{6, 5}); REQUIRE(values == std::vector<int> {6, 5});
} }
SECTION("const operator[]") { SECTION("const operator[]")
{
REQUIRE(constBuf[0] == 5); REQUIRE(constBuf[0] == 5);
REQUIRE(constBuf[1] == 6); REQUIRE(constBuf[1] == 6);
} }
} }
TEST_CASE("Dynamic RingBuffer behaves like static", "[RingBuffer][Dynamic]") { TEST_CASE("Dynamic RingBuffer behaves like static", "[RingBuffer][Dynamic]")
{
RingBuffer<int> buf(5); RingBuffer<int> buf(5);
for (int i = 0; i < 5; ++i) for (int i = 0; i < 5; ++i)
@@ -443,12 +506,13 @@ TEST_CASE("Dynamic RingBuffer behaves like static", "[RingBuffer][Dynamic]") {
auto val = buf.PopBack(); auto val = buf.PopBack();
REQUIRE(val == 0); REQUIRE(val == 0);
REQUIRE(buf.Count() == 4); REQUIRE(buf.Count() == 4);
SECTION("Dynamic buffer wraparound") { SECTION("Dynamic buffer wraparound")
{
buf.Push(50); buf.Push(50);
REQUIRE(buf.Front() == 50); REQUIRE(buf.Front() == 50);
REQUIRE(buf.Back() == 10); REQUIRE(buf.Back() == 10);
buf.Push(60); buf.Push(60);
REQUIRE(buf[0] == 20); REQUIRE(buf[0] == 20);
REQUIRE(buf[1] == 30); REQUIRE(buf[1] == 30);
@@ -458,112 +522,133 @@ TEST_CASE("Dynamic RingBuffer behaves like static", "[RingBuffer][Dynamic]") {
} }
} }
TEST_CASE("RingBuffer with complex types", "[RingBuffer][Complex]") { TEST_CASE("RingBuffer with complex types", "[RingBuffer][Complex]")
{
RingBuffer<std::vector<int>, 3> buf; RingBuffer<std::vector<int>, 3> buf;
buf.Push(std::vector<int>{1, 2, 3}); buf.Push(std::vector<int> {1, 2, 3});
buf.Emplace(std::initializer_list<int>{4, 5, 6}); buf.Emplace(std::initializer_list<int> {4, 5, 6});
REQUIRE(buf.Count() == 2); REQUIRE(buf.Count() == 2);
REQUIRE(buf.Front()[0] == 4); REQUIRE(buf.Front()[0] == 4);
REQUIRE(buf.Front()[2] == 6); REQUIRE(buf.Front()[2] == 6);
REQUIRE(buf.Back()[0] == 1); REQUIRE(buf.Back()[0] == 1);
REQUIRE(buf.Back()[2] == 3); REQUIRE(buf.Back()[2] == 3);
const auto ret = buf.PushFront(std::vector<int>{7, 8, 9}); const auto ret = buf.PushFront(std::vector<int> {7, 8, 9});
REQUIRE(!ret.has_value()); REQUIRE(!ret.has_value());
REQUIRE(buf.Front()[0] == 7); REQUIRE(buf.Front()[0] == 7);
buf.Clear(); buf.Clear();
REQUIRE(buf.IsEmpty()); REQUIRE(buf.IsEmpty());
} }
TEST_CASE("Iterator operations with wrapped buffer", "[RingBuffer][Iterators]") { TEST_CASE("Iterator operations with wrapped buffer", "[RingBuffer][Iterators]")
{
RingBuffer<int, 5> buf; RingBuffer<int, 5> buf;
// Fill buffer // Fill buffer
for (int i = 1; i <= 5; i++) { for (int i = 1; i <= 5; i++)
{
buf.Push(i); buf.Push(i);
} }
// Force wraparound // Force wraparound
buf.PopBack(); buf.PopBack();
buf.PopBack(); buf.PopBack();
buf.Push(6); buf.Push(6);
buf.Push(7); buf.Push(7);
// Buffer should now be: 3, 4, 5, 6, 7 with internal wraparound // Buffer should now be: 3, 4, 5, 6, 7 with internal wraparound
std::vector<int> values; std::vector<int> values;
for (auto val : buf) { for (auto val: buf)
{
values.push_back(val); values.push_back(val);
} }
REQUIRE(values == std::vector<int>{3, 4, 5, 6, 7}); REQUIRE(values == std::vector<int> {3, 4, 5, 6, 7});
std::vector<int> reverseValues; std::vector<int> reverseValues;
for (auto it = buf.rbegin(); it != buf.rend(); ++it) { for (auto it = buf.rbegin(); it != buf.rend(); ++it)
{
reverseValues.push_back(*it); reverseValues.push_back(*it);
} }
REQUIRE(reverseValues == std::vector<int>{7, 6, 5, 4, 3}); REQUIRE(reverseValues == std::vector<int> {7, 6, 5, 4, 3});
} }
TEST_CASE("Edge cases", "[RingBuffer][EdgeCases]") { TEST_CASE("Edge cases", "[RingBuffer][EdgeCases]")
SECTION("Capacity of 1") { {
SECTION("Capacity of 1")
{
RingBuffer<int, 1> buf; RingBuffer<int, 1> buf;
REQUIRE(buf.Capacity() == 1); REQUIRE(buf.Capacity() == 1);
REQUIRE(buf.IsEmpty()); REQUIRE(buf.IsEmpty());
buf.Push(42); buf.Push(42);
REQUIRE(buf.Front() == 42); REQUIRE(buf.Front() == 42);
REQUIRE(buf.Back() == 42); REQUIRE(buf.Back() == 42);
REQUIRE_FALSE(buf.HasFree()); REQUIRE_FALSE(buf.HasFree());
buf.Push(99); buf.Push(99);
REQUIRE(buf.Front() == 99); REQUIRE(buf.Front() == 99);
REQUIRE(buf.Back() == 99); REQUIRE(buf.Back() == 99);
buf.PopFront(); buf.PopFront();
REQUIRE(buf.IsEmpty()); REQUIRE(buf.IsEmpty());
REQUIRE_THROWS_AS(buf.PopFront(), std::underflow_error); REQUIRE_THROWS_AS(buf.PopFront(), std::underflow_error);
} }
SECTION("Dynamic buffer with size 1") { SECTION("Dynamic buffer with size 1")
{
RingBuffer<int> buf(1); RingBuffer<int> buf(1);
buf.Push(42); buf.Push(42);
REQUIRE(buf.Front() == 42); REQUIRE(buf.Front() == 42);
REQUIRE_FALSE(buf.HasFree()); REQUIRE_FALSE(buf.HasFree());
buf.Push(99); buf.Push(99);
REQUIRE(buf.Front() == 99); REQUIRE(buf.Front() == 99);
} }
} }
TEST_CASE("Algorithm compatibility", "[RingBuffer][Algorithms]") { TEST_CASE("Algorithm compatibility", "[RingBuffer][Algorithms]")
{
RingBuffer<int, 5> buf; RingBuffer<int, 5> buf;
for (int i = 1; i <= 5; i++) { for (int i = 1; i <= 5; i++)
{
buf.Push(i); buf.Push(i);
} }
SECTION("std::find") { SECTION("std::find")
{
auto it = std::find(buf.begin(), buf.end(), 3); auto it = std::find(buf.begin(), buf.end(), 3);
REQUIRE(it != buf.end()); REQUIRE(it != buf.end());
REQUIRE(*it == 3); REQUIRE(*it == 3);
auto notFound = std::find(buf.begin(), buf.end(), 99); auto notFound = std::find(buf.begin(), buf.end(), 99);
REQUIRE(notFound == buf.end()); REQUIRE(notFound == buf.end());
} }
SECTION("std::count") { SECTION("std::count")
{
buf.Push(3); // Overwrites the 1 buf.Push(3); // Overwrites the 1
int count = std::count(buf.begin(), buf.end(), 3); int count = std::count(buf.begin(), buf.end(), 3);
REQUIRE(count == 2); REQUIRE(count == 2);
} }
SECTION("std::copy") { SECTION("std::copy")
{
std::vector<int> target(5); std::vector<int> target(5);
std::copy(buf.begin(), buf.end(), target.begin()); std::copy(buf.begin(), buf.end(), target.begin());
REQUIRE(target == std::vector<int>{1, 2, 3, 4, 5}); REQUIRE(target == std::vector<int> {1, 2, 3, 4, 5});
} }
}
TEST_CASE("Class size", "[RingBuffer]")
{
REQUIRE(sizeof(RingBuffer<int>) == 32);
REQUIRE(sizeof(RingBuffer<int, 1>) == 24);
REQUIRE(sizeof(RingBuffer<int, 2>) == 24);
REQUIRE(sizeof(RingBuffer<int, 4>) == 32);
} }

View File

@@ -13,161 +13,162 @@ using namespace OpenVulkano::Math;
TEST_CASE("RunningAverage Basic Functionality - Integer Type", "[RunningAverage]") TEST_CASE("RunningAverage Basic Functionality - Integer Type", "[RunningAverage]")
{ {
SECTION("Dynamic Size Constructor with Default Value") SECTION("Dynamic Size Constructor with Default Value")
{ {
RunningAverage<int> avg(5); RunningAverage<int> avg(5);
REQUIRE(avg.Get() == 0); REQUIRE(avg.Get() == 0);
} }
SECTION("Dynamic Size Constructor with Custom Default") SECTION("Dynamic Size Constructor with Custom Default")
{ {
RunningAverage<int> avg(5, 10); RunningAverage<int> avg(5, 10);
REQUIRE(avg.Get() == 10); REQUIRE(avg.Get() == 10);
} }
SECTION("Static Size Constructor with Default Value") SECTION("Static Size Constructor with Default Value")
{ {
RunningAverage<int, 5> avg; RunningAverage<int, 5> avg;
REQUIRE(avg.Get() == 0); REQUIRE(avg.Get() == 0);
} }
SECTION("Static Size Constructor with Custom Default") SECTION("Static Size Constructor with Custom Default")
{ {
RunningAverage<int, 5> avg(10); RunningAverage<int, 5> avg(10);
REQUIRE(avg.Get() == 10); REQUIRE(avg.Get() == 10);
} }
SECTION("Push Single Value") SECTION("Push Single Value")
{ {
RunningAverage<int> avg(5, 0); RunningAverage<int> avg(5, 0);
avg.Push(5); avg.Push(5);
REQUIRE(avg.Get() == 1); // (0+0+0+0+5)/5 = 1 REQUIRE(avg.Get() == 1); // (0+0+0+0+5)/5 = 1
} }
SECTION("Push Multiple Values") SECTION("Push Multiple Values")
{ {
RunningAverage<int> avg(4, 0); RunningAverage<int> avg(4, 0);
avg.Push(4); avg.Push(4);
avg.Push(8); avg.Push(8);
avg.Push(12); avg.Push(12);
avg.Push(16); avg.Push(16);
REQUIRE(avg.Get() == 10); // (4+8+12+16)/4 = 10 REQUIRE(avg.Get() == 10); // (4+8+12+16)/4 = 10
} }
SECTION("Push with Overwrite") SECTION("Push with Overwrite")
{ {
RunningAverage<int> avg(3, 0); RunningAverage<int> avg(3, 0);
avg.Push(3); avg.Push(3);
avg.Push(6); avg.Push(6);
avg.Push(9); avg.Push(9);
REQUIRE(avg.Get() == 6); // (3+6+9)/3 = 6 REQUIRE(avg.Get() == 6); // (3+6+9)/3 = 6
avg.Push(12); // Overwrite 3 avg.Push(12); // Overwrite 3
REQUIRE(avg.Get() == 9); // (6+9+12)/3 = 9 REQUIRE(avg.Get() == 9); // (6+9+12)/3 = 9
} }
} }
TEST_CASE("RunningAverage with Floating Point Types", "[RunningAverage]") TEST_CASE("RunningAverage with Floating Point Types", "[RunningAverage]")
{ {
SECTION("Basic Floating Point Operation") SECTION("Basic Floating Point Operation")
{ {
RunningAverage<double> avg(3, 0.0); RunningAverage<double> avg(3, 0.0);
avg.Push(1.5); avg.Push(1.5);
avg.Push(2.5); avg.Push(2.5);
avg.Push(3.5); avg.Push(3.5);
REQUIRE(avg.Get() == Catch::Approx(2.5)); // (1.5+2.5+3.5)/3 = 2.5 REQUIRE(avg.Get() == Catch::Approx(2.5)); // (1.5+2.5+3.5)/3 = 2.5
} }
SECTION("Recalculation with Floating Point") SECTION("Recalculation with Floating Point")
{ {
// Set recalculation to happen after 3 operations // Set recalculation to happen after 3 operations
RunningAverage<double, std::numeric_limits<size_t>::max(), true, 3> avg(3, 0.0); RunningAverage<double, std::numeric_limits<size_t>::max(), true, 3> avg(3, 0.0);
avg.Push(1.0); avg.Push(1.0);
avg.Push(2.0); avg.Push(2.0);
avg.Push(3.0); // This should trigger recalculation avg.Push(3.0); // This should trigger recalculation
REQUIRE(avg.Get() == Catch::Approx(2.0)); // (1.0+2.0+3.0)/3 = 2.0 REQUIRE(avg.Get() == Catch::Approx(2.0)); // (1.0+2.0+3.0)/3 = 2.0
// Push another value to check that recalculation didn't affect the result // Push another value to check that recalculation didn't affect the result
avg.Push(4.0); avg.Push(4.0);
REQUIRE(avg.Get() == Catch::Approx(3.0)); // (2.0+3.0+4.0)/3 = 3.0 REQUIRE(avg.Get() == Catch::Approx(3.0)); // (2.0+3.0+4.0)/3 = 3.0
} }
SECTION("Manual Recalculation") SECTION("Manual Recalculation")
{ {
RunningAverage<double> avg(3, 0.0); RunningAverage<double> avg(3, 0.0);
avg.Push(1.5); avg.Push(1.5);
avg.Push(2.5); avg.Push(2.5);
avg.Push(3.5); avg.Push(3.5);
// Force recalculation and verify it doesn't change the result // Force recalculation and verify it doesn't change the result
avg.Recalculate(); avg.Recalculate();
REQUIRE(avg.Get() == Catch::Approx(2.5)); REQUIRE(avg.Get() == Catch::Approx(2.5));
} }
} }
TEST_CASE("RunningAverage Edge Cases", "[RunningAverage]") TEST_CASE("RunningAverage Edge Cases", "[RunningAverage]")
{ {
SECTION("Large Values") SECTION("Large Values")
{ {
RunningAverage<int> avg(3, 0); RunningAverage<int> avg(3, 0);
avg.Push(1000000); avg.Push(1000000);
avg.Push(2000000); avg.Push(2000000);
avg.Push(3000000); avg.Push(3000000);
REQUIRE(avg.Get() == 2000000); REQUIRE(avg.Get() == 2000000);
} }
SECTION("Negative Values") SECTION("Negative Values")
{ {
RunningAverage<int> avg(3, 0); RunningAverage<int> avg(3, 0);
avg.Push(-10); avg.Push(-10);
avg.Push(-20); avg.Push(-20);
avg.Push(-30); avg.Push(-30);
REQUIRE(avg.Get() == -20); REQUIRE(avg.Get() == -20);
} }
SECTION("Mixed Sign Values") SECTION("Mixed Sign Values")
{ {
RunningAverage<int> avg(4, 0); RunningAverage<int> avg(4, 0);
avg.Push(-10); avg.Push(-10);
avg.Push(10); avg.Push(10);
avg.Push(-10); avg.Push(-10);
avg.Push(10); avg.Push(10);
REQUIRE(avg.Get() == 0); REQUIRE(avg.Get() == 0);
} }
} }
TEST_CASE("Custom Types with RunningAverage", "[RunningAverage]") TEST_CASE("Custom Types with RunningAverage", "[RunningAverage]")
{ {
SECTION("Vector Type Average") SECTION("Vector Type Average")
{ {
RunningAverage<Vector2f> avg(3, Vector2f(0, 0)); RunningAverage<Vector2f> avg(3, Vector2f(0, 0));
avg.Push(Vector2f(1, 2)); avg.Push(Vector2f(1, 2));
avg.Push(Vector2f(3, 4)); avg.Push(Vector2f(3, 4));
avg.Push(Vector2f(5, 6)); avg.Push(Vector2f(5, 6));
Vector2f result = avg.Get(); Vector2f result = avg.Get();
REQUIRE(result.x == Catch::Approx(3.0f)); REQUIRE(result.x == Catch::Approx(3.0f));
REQUIRE(result.y == Catch::Approx(4.0f)); REQUIRE(result.y == Catch::Approx(4.0f));
} }
} }
// This test specifically checks the correctness of recalculation for floating point // This test specifically checks the correctness of recalculation for floating point
TEST_CASE("Precision with Floating Point Types", "[RunningAverage]") TEST_CASE("Precision with Floating Point Types", "[RunningAverage]")
{ {
SECTION("Accumulation Error Correction") SECTION("Accumulation Error Correction")
{ {
// Create a running average that would normally suffer from floating point errors // Create a running average that would normally suffer from floating point errors
// Force recalculation after 20 operations // Force recalculation after 20 operations
RunningAverage<double, std::numeric_limits<size_t>::max(), true, 20> avg(5, 0.0); RunningAverage<double, std::numeric_limits<size_t>::max(), true, 20> avg(5, 0.0);
// Push values that would cause floating point rounding errors // Push values that would cause floating point rounding errors
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++)
avg.Push(0.1); // 0.1 cannot be represented exactly in binary floating point {
} avg.Push(0.1); // 0.1 cannot be represented exactly in binary floating point
}
// Check that the result is still accurate due to periodic recalculation
REQUIRE(avg.Get() == Catch::Approx(0.1).epsilon(0.0001)); // Check that the result is still accurate due to periodic recalculation
} REQUIRE(avg.Get() == Catch::Approx(0.1).epsilon(0.0001));
}
} }
TEST_CASE("Class size", "[RunningAverage]") TEST_CASE("Class size", "[RunningAverage]")