/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #include #include "Data/Containers/RingBuffer.hpp" #include #include #include #include #include using namespace OpenVulkano; // Helper to track construction/destruction struct Tracked { static inline int ctorCount = 0; static inline int dtorCount = 0; static inline int moveCount = 0; static inline int copyCount = 0; int value; 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() { ++dtorCount; } Tracked& operator=(const Tracked& other) { value = other.value; ++copyCount; return *this; } Tracked& operator=(Tracked&& other) noexcept { value = other.value; ++moveCount; return *this; } friend bool operator==(const Tracked& lhs, const Tracked& rhs) { return lhs.value == rhs.value; } }; void reset_tracking() { Tracked::ctorCount = 0; Tracked::dtorCount = 0; Tracked::moveCount = 0; Tracked::copyCount = 0; } TEST_CASE("RingBuffer Basic Construction", "[RingBuffer]") { SECTION("Static buffer construction") { RingBuffer buf; REQUIRE(buf.Capacity() == 5); REQUIRE(buf.IsEmpty()); REQUIRE(buf.Count() == 0); REQUIRE(buf.HasFree()); } SECTION("Dynamic buffer construction") { RingBuffer buf(10); REQUIRE(buf.Capacity() == 10); REQUIRE(buf.IsEmpty()); REQUIRE(buf.Count() == 0); REQUIRE(buf.HasFree()); } } TEST_CASE("RingBuffer Tracking Destruction", "[RingBuffer][Tracked]") { reset_tracking(); { RingBuffer buf; buf.Push(Tracked(1)); buf.Push(Tracked(2)); buf.PopFront(); buf.Clear(); } REQUIRE(Tracked::dtorCount == Tracked::ctorCount); } TEST_CASE("RingBuffer Move and Copy Semantics", "[RingBuffer][Tracked]") { reset_tracking(); { RingBuffer buf; SECTION("Push with move") { Tracked t(42); buf.Push(std::move(t)); REQUIRE(Tracked::moveCount >= 1); REQUIRE(buf.Front().value == 42); } SECTION("Push with copy") { Tracked t(42); buf.Push(t); REQUIRE(Tracked::copyCount >= 1); REQUIRE(buf.Front().value == 42); } SECTION("Emplace constructs in-place") { int initialCtors = Tracked::ctorCount; buf.Emplace(99); REQUIRE(Tracked::ctorCount == initialCtors + 1); REQUIRE(buf.Front().value == 99); } } REQUIRE(Tracked::dtorCount == Tracked::ctorCount); } TEST_CASE("RingBuffer Push and Pop Operations", "[RingBuffer]") { RingBuffer buf; buf.Push(1); buf.Push(2); buf.Push(3); REQUIRE(buf.Count() == 3); REQUIRE_FALSE(buf.HasFree()); SECTION("PopFront/Back correctness") { REQUIRE(buf.PopBack() == 1); REQUIRE(buf.PopFront() == 3); REQUIRE(buf.Count() == 1); REQUIRE(buf.PopFront() == 2); REQUIRE(buf.IsEmpty()); REQUIRE_THROWS_AS(buf.PopFront(), std::underflow_error); REQUIRE_THROWS_AS(buf.PopBack(), std::underflow_error); } SECTION("Clear empties the buffer") { buf.Clear(); REQUIRE(buf.IsEmpty()); REQUIRE(buf.Count() == 0); } SECTION("Front and Back access") { REQUIRE(buf.Front() == 3); REQUIRE(buf.Back() == 1); buf.PopFront(); REQUIRE(buf.Front() == 2); REQUIRE(buf.Back() == 1); buf.PopBack(); REQUIRE(buf.Front() == 2); REQUIRE(buf.Back() == 2); } } TEST_CASE("RingBuffer PushNoOverwrite operation", "[RingBuffer]") { RingBuffer buf; buf.PushNoOverwrite(1); buf.PushNoOverwrite(2); REQUIRE_THROWS_AS(buf.PushNoOverwrite(3), std::overflow_error); SECTION("EmplaceNoOverwrite") { RingBuffer strBuf; strBuf.EmplaceNoOverwrite("first"); strBuf.EmplaceNoOverwrite("second"); REQUIRE_THROWS_AS(strBuf.EmplaceNoOverwrite("third"), std::overflow_error); } } TEST_CASE("RingBuffer PushAndOverwrite operation", "[RingBuffer]") { RingBuffer buf; SECTION("Cannot use PushAndOverwrite when buffer not full") { buf.Push(1); REQUIRE_THROWS_AS(buf.PushAndOverwrite(5), std::runtime_error); } SECTION("PushAndOverwrite returns overwritten value") { buf.Fill(42); REQUIRE(buf.Count() == 2); int overwritten = buf.PushAndOverwrite(99); REQUIRE(overwritten == 42); REQUIRE(buf.Front() == 99); REQUIRE(buf.Back() == 42); } } TEST_CASE("PushBack and PushFront Overwrite Logic", "[RingBuffer]") { RingBuffer buf; buf.Push(1); buf.Push(2); SECTION("PushFront overwrites correctly") { auto overwrittenFront = buf.PushFront(3); REQUIRE(overwrittenFront.has_value()); REQUIRE(overwrittenFront.value() == 1); REQUIRE(buf.Front() == 3); REQUIRE(buf.Back() == 2); } SECTION("PushBack overwrites correctly") { auto overwrittenBack = buf.PushBack(4); REQUIRE(overwrittenBack.has_value()); REQUIRE(overwrittenBack.value() == 1); REQUIRE(buf.Front() == 2); REQUIRE(buf.Back() == 4); } SECTION("PushFront with available space doesn't overwrite") { RingBuffer buf2; buf2.Push(10); auto result = buf2.PushFront(20); REQUIRE_FALSE(result.has_value()); REQUIRE(buf2.Count() == 2); REQUIRE(buf2.Front() == 20); REQUIRE(buf2.Back() == 10); } SECTION("PushBack with available space doesn't overwrite") { RingBuffer buf2; buf2.Push(10); auto result = buf2.PushBack(20); REQUIRE_FALSE(result.has_value()); REQUIRE(buf2.Count() == 2); REQUIRE(buf2.Front() == 10); REQUIRE(buf2.Back() == 20); } } TEST_CASE("at() bounds checking", "[RingBuffer]") { RingBuffer buf; buf.Push(100); buf.Push(200); REQUIRE(buf.at(0) == 100); REQUIRE(buf.at(1) == 200); REQUIRE_THROWS_AS(buf.at(2), std::range_error); SECTION("const at() works correctly") { const auto& constBuf = buf; REQUIRE(constBuf.at(0) == 100); REQUIRE(constBuf.at(1) == 200); REQUIRE_THROWS_AS(constBuf.at(2), std::range_error); } } TEST_CASE("Index-based Access and Wraparound", "[RingBuffer]") { RingBuffer buf; buf.Push(1); buf.Push(2); buf.Push(3); REQUIRE(buf[0] == 1); REQUIRE(buf[1] == 2); REQUIRE(buf[2] == 3); buf.PopBack(); buf.Push(4); REQUIRE(buf[0] == 2); REQUIRE(buf[1] == 3); REQUIRE(buf[2] == 4); // Test full wraparound buf.Push(5); buf.Push(6); REQUIRE(buf[0] == 4); REQUIRE(buf[1] == 5); REQUIRE(buf[2] == 6); SECTION("Wraparound with power-of-2 size") { RingBuffer powBuf; // Power of 2 size for different code path for (int i = 1; i <= 4; i++) { powBuf.Push(i); } REQUIRE(powBuf[0] == 1); REQUIRE(powBuf[3] == 4); powBuf.PopBack(); powBuf.Push(5); REQUIRE(powBuf[0] == 2); REQUIRE(powBuf[3] == 5); } } TEST_CASE("Fill operation", "[RingBuffer]") { RingBuffer buf; SECTION("Fill empty buffer") { buf.Fill(42); REQUIRE(buf.Count() == 5); REQUIRE_FALSE(buf.HasFree()); for (int i = 0; i < 5; i++) { REQUIRE(buf[i] == 42); } } SECTION("Fill partially full buffer") { buf.Push(1); buf.Push(2); buf.Fill(99); REQUIRE(buf.Count() == 5); REQUIRE_FALSE(buf.HasFree()); REQUIRE(buf[0] == 1); REQUIRE(buf[1] == 2); REQUIRE(buf[2] == 99); REQUIRE(buf[3] == 99); REQUIRE(buf[4] == 99); } } TEST_CASE("Emplace and EmplaceBack/Front work correctly", "[RingBuffer]") { RingBuffer buf; buf.Emplace("first"); buf.EmplaceBack("second"); REQUIRE(buf.Count() == 2); REQUIRE(buf[1] == "first"); REQUIRE(buf[0] == "second"); auto overwritten = buf.EmplaceFront("new"); REQUIRE(overwritten.has_value()); REQUIRE(overwritten.value() == "second"); REQUIRE(buf.Front() == "new"); SECTION("EmplaceNoOverwrite throws when full") { RingBuffer buf2; buf2.EmplaceNoOverwrite("one"); buf2.EmplaceNoOverwrite("two"); REQUIRE_THROWS_AS(buf2.EmplaceNoOverwrite("three"), std::overflow_error); } } TEST_CASE("Iterators forward and reverse", "[RingBuffer]") { RingBuffer buf; buf.Push(10); buf.Push(20); buf.Push(30); SECTION("Forward iteration") { std::vector forward; for (int val: buf) forward.push_back(val); REQUIRE(forward == std::vector {10, 20, 30}); } SECTION("Reverse iteration") { std::vector reverse; for (auto it = buf.rbegin(); it != buf.rend(); ++it) reverse.push_back(*it); REQUIRE(reverse == std::vector {30, 20, 10}); } SECTION("Iterator decrement") { auto it = buf.end(); --it; REQUIRE(*it == 30); --it; REQUIRE(*it == 20); --it; REQUIRE(*it == 10); } SECTION("Reverse iterator decrement") { auto it = buf.rend(); --it; REQUIRE(*it == 10); --it; REQUIRE(*it == 20); --it; REQUIRE(*it == 30); } SECTION("Iterator post-increment/decrement") { auto it = buf.begin(); REQUIRE(*(it++) == 10); REQUIRE(*it == 20); auto it2 = --buf.end(); REQUIRE(*(it2--) == 30); REQUIRE(*it2 == 20); } SECTION("Empty buffer iteration") { RingBuffer emptyBuf; REQUIRE(emptyBuf.begin() == emptyBuf.end()); REQUIRE(emptyBuf.rbegin() == emptyBuf.rend()); } } TEST_CASE("Const correctness in iterators", "[RingBuffer][Const]") { RingBuffer buf; buf.Push(5); buf.Push(6); const auto& constBuf = buf; SECTION("const_iterator usage") { std::ostringstream oss; for (auto it = constBuf.cbegin(); it != constBuf.cend(); ++it) { oss << *it << " "; } REQUIRE(oss.str() == "5 6 "); } SECTION("const rbegin/rend usage") { std::vector values; for (auto it = constBuf.crbegin(); it != constBuf.crend(); ++it) { values.push_back(*it); } REQUIRE(values == std::vector {6, 5}); } SECTION("const operator[]") { REQUIRE(constBuf[0] == 5); REQUIRE(constBuf[1] == 6); } } TEST_CASE("Dynamic RingBuffer behaves like static", "[RingBuffer][Dynamic]") { RingBuffer buf(5); for (int i = 0; i < 5; ++i) buf.Push(i * 10); REQUIRE(buf.Count() == 5); REQUIRE_FALSE(buf.HasFree()); REQUIRE(buf.Front() == 40); REQUIRE(buf.Back() == 0); auto val = buf.PopBack(); REQUIRE(val == 0); REQUIRE(buf.Count() == 4); SECTION("Dynamic buffer wraparound") { buf.Push(50); REQUIRE(buf.Front() == 50); REQUIRE(buf.Back() == 10); buf.Push(60); REQUIRE(buf[0] == 20); REQUIRE(buf[1] == 30); REQUIRE(buf[2] == 40); REQUIRE(buf[3] == 50); REQUIRE(buf[4] == 60); } } TEST_CASE("RingBuffer with complex types", "[RingBuffer][Complex]") { RingBuffer, 3> buf; buf.Push(std::vector {1, 2, 3}); buf.Emplace(std::initializer_list {4, 5, 6}); REQUIRE(buf.Count() == 2); REQUIRE(buf.Front()[0] == 4); REQUIRE(buf.Front()[2] == 6); REQUIRE(buf.Back()[0] == 1); REQUIRE(buf.Back()[2] == 3); const auto ret = buf.PushFront(std::vector {7, 8, 9}); REQUIRE(!ret.has_value()); REQUIRE(buf.Front()[0] == 7); buf.Clear(); REQUIRE(buf.IsEmpty()); } TEST_CASE("Iterator operations with wrapped buffer", "[RingBuffer][Iterators]") { RingBuffer buf; // Fill buffer for (int i = 1; i <= 5; i++) { buf.Push(i); } // Force wraparound buf.PopBack(); buf.PopBack(); buf.Push(6); buf.Push(7); // Buffer should now be: 3, 4, 5, 6, 7 with internal wraparound std::vector values; for (auto val: buf) { values.push_back(val); } REQUIRE(values == std::vector {3, 4, 5, 6, 7}); std::vector reverseValues; for (auto it = buf.rbegin(); it != buf.rend(); ++it) { reverseValues.push_back(*it); } REQUIRE(reverseValues == std::vector {7, 6, 5, 4, 3}); } TEST_CASE("Edge cases", "[RingBuffer][EdgeCases]") { SECTION("Capacity of 1") { RingBuffer buf; REQUIRE(buf.Capacity() == 1); REQUIRE(buf.IsEmpty()); buf.Push(42); REQUIRE(buf.Front() == 42); REQUIRE(buf.Back() == 42); REQUIRE_FALSE(buf.HasFree()); buf.Push(99); REQUIRE(buf.Front() == 99); REQUIRE(buf.Back() == 99); buf.PopFront(); REQUIRE(buf.IsEmpty()); REQUIRE_THROWS_AS(buf.PopFront(), std::underflow_error); } SECTION("Dynamic buffer with size 1") { RingBuffer buf(1); buf.Push(42); REQUIRE(buf.Front() == 42); REQUIRE_FALSE(buf.HasFree()); buf.Push(99); REQUIRE(buf.Front() == 99); } } TEST_CASE("Algorithm compatibility", "[RingBuffer][Algorithms]") { RingBuffer buf; for (int i = 1; i <= 5; i++) { buf.Push(i); } SECTION("std::find") { auto it = std::find(buf.begin(), buf.end(), 3); REQUIRE(it != buf.end()); REQUIRE(*it == 3); auto notFound = std::find(buf.begin(), buf.end(), 99); REQUIRE(notFound == buf.end()); } SECTION("std::count") { buf.Push(3); // Overwrites the 1 int count = std::count(buf.begin(), buf.end(), 3); REQUIRE(count == 2); } SECTION("std::copy") { std::vector target(5); std::copy(buf.begin(), buf.end(), target.begin()); REQUIRE(target == std::vector {1, 2, 3, 4, 5}); } } TEST_CASE("Class size", "[RingBuffer]") { REQUIRE(sizeof(RingBuffer) == 32); REQUIRE(sizeof(RingBuffer) == 24); REQUIRE(sizeof(RingBuffer) == 24); REQUIRE(sizeof(RingBuffer) == 32); }