// g++ --std=c++11 test.cpp #include "test.h" #include "test.h" #include "fl/circular_buffer.h" #include "fl/namespace.h" using namespace fl; TEST_CASE("circular_buffer basic operations") { CircularBuffer buffer(5); CHECK(buffer.empty()); CHECK_EQ(buffer.size(), 0); buffer.push_back(1); buffer.push_back(2); buffer.push_back(3); CHECK_EQ(buffer.size(), 3); CHECK_FALSE(buffer.empty()); CHECK_FALSE(buffer.full()); CHECK_EQ(buffer.front(), 1); CHECK_EQ(buffer.back(), 3); int value; CHECK_EQ(buffer.pop_front(&value), true); CHECK_EQ(value, 1); CHECK_EQ(buffer.size(), 2); CHECK_EQ(buffer.front(), 2); } TEST_CASE("circular_buffer operator[]") { CircularBuffer buffer(5); CHECK(buffer.empty()); CHECK_EQ(buffer.size(), 0); buffer.push_back(1); buffer.push_back(2); CHECK_EQ(buffer.size(), 2); CHECK_EQ(buffer[0], 1); CHECK_EQ(buffer[1], 2); buffer.pop_front(nullptr); CHECK_EQ(2, buffer[0]); buffer.push_back(3); CHECK_EQ(2, buffer[0]); CHECK_EQ(3, buffer[1]); buffer.pop_back(nullptr); CHECK_EQ(2, buffer[0]); } TEST_CASE("circular_buffer overflow behavior") { CircularBuffer buffer(3); buffer.push_back(1); buffer.push_back(2); buffer.push_back(3); CHECK(buffer.full()); buffer.push_back(4); CHECK(buffer.full()); CHECK_EQ(buffer.size(), 3); int value; CHECK_EQ(buffer.pop_front(&value), true); CHECK_EQ(value, 2); CHECK_EQ(buffer.pop_front(&value), true); CHECK_EQ(value, 3); CHECK_EQ(buffer.pop_front(&value), true); CHECK_EQ(value, 4); CHECK(buffer.empty()); // CHECK_EQ(buffer.pop_front(), 0); // Returns default-constructed int (0) when empty CHECK_EQ(buffer.pop_front(&value), false); } TEST_CASE("circular_buffer edge cases") { CircularBuffer buffer(1); CHECK(buffer.empty()); CHECK_FALSE(buffer.full()); buffer.push_back(42); CHECK_FALSE(buffer.empty()); CHECK(buffer.full()); buffer.push_back(43); CHECK_EQ(buffer.front(), 43); CHECK_EQ(buffer.back(), 43); int value; bool ok = buffer.pop_front(&value); CHECK(ok); CHECK_EQ(value, 43); CHECK(buffer.empty()); } TEST_CASE("circular_buffer clear operation") { CircularBuffer buffer(5); buffer.push_back(1); buffer.push_back(2); buffer.push_back(3); CHECK_EQ(buffer.size(), 3); buffer.clear(); CHECK(buffer.empty()); CHECK_EQ(buffer.size(), 0); buffer.push_back(4); CHECK_EQ(buffer.front(), 4); CHECK_EQ(buffer.back(), 4); } TEST_CASE("circular_buffer indexing") { CircularBuffer buffer(5); buffer.push_back(10); buffer.push_back(20); buffer.push_back(30); CHECK_EQ(buffer[0], 10); CHECK_EQ(buffer[1], 20); CHECK_EQ(buffer[2], 30); buffer.pop_front(nullptr); buffer.push_back(40); CHECK_EQ(buffer[0], 20); CHECK_EQ(buffer[1], 30); CHECK_EQ(buffer[2], 40); } TEST_CASE("circular_buffer with custom type") { struct CustomType { int value; CustomType(int v = 0) : value(v) {} bool operator==(const CustomType& other) const { return value == other.value; } }; CircularBuffer buffer(3); buffer.push_back(CustomType(1)); buffer.push_back(CustomType(2)); buffer.push_back(CustomType(3)); CHECK_EQ(buffer.front().value, 1); CHECK_EQ(buffer.back().value, 3); buffer.push_back(CustomType(4)); CustomType value; CHECK_EQ(buffer.pop_front(&value), true); CHECK_EQ(value.value, 2); CHECK_EQ(buffer.pop_front(&value), true); CHECK_EQ(value.value, 3); CHECK_EQ(buffer.pop_front(&value), true); CHECK_EQ(value.value, 4); } TEST_CASE("circular_buffer writing to full buffer") { CircularBuffer buffer(3); // Fill the buffer buffer.push_back(1); buffer.push_back(2); buffer.push_back(3); CHECK(buffer.full()); // Write to full buffer buffer.push_back(4); CHECK(buffer.full()); CHECK_EQ(buffer.size(), 3); // Check that the oldest element was overwritten CHECK_EQ(buffer[0], 2); CHECK_EQ(buffer[1], 3); CHECK_EQ(buffer[2], 4); // Write multiple elements to full buffer buffer.push_back(5); buffer.push_back(6); CHECK(buffer.full()); CHECK_EQ(buffer.size(), 3); // Check that the buffer contains only the most recent elements CHECK_EQ(buffer[0], 4); CHECK_EQ(buffer[1], 5); CHECK_EQ(buffer[2], 6); // Verify front() and back() CHECK_EQ(buffer.front(), 4); CHECK_EQ(buffer.back(), 6); // Pop all elements and verify //CHECK_EQ(buffer.pop_front(), 4); //CHECK_EQ(buffer.pop_front(), 5); //CHECK_EQ(buffer.pop_front(), 6); int value; CHECK_EQ(buffer.pop_front(&value), true); CHECK_EQ(value, 4); CHECK_EQ(buffer.pop_front(&value), true); CHECK_EQ(value, 5); CHECK_EQ(buffer.pop_front(&value), true); CHECK_EQ(value, 6); CHECK(buffer.empty()); } #if 1 TEST_CASE("circular_buffer zero capacity") { CircularBuffer buffer(0); CHECK(buffer.empty()); CHECK(buffer.full()); CHECK_EQ(buffer.size(), 0); // Attempt to push an element buffer.push_back(1); // Buffer should now contain one element CHECK(buffer.empty()); CHECK(buffer.full()); CHECK_EQ(buffer.size(), 0); // Attempt to pop an element // CHECK_EQ(buffer.pop_front(), 0); int value; CHECK_EQ(buffer.pop_front(&value), false); // Buffer should be empty again CHECK(buffer.empty()); CHECK(buffer.full()); CHECK_EQ(buffer.size(), 0); } #endif TEST_CASE("circular_buffer pop_back operation") { CircularBuffer buffer(5); buffer.push_back(1); buffer.push_back(2); buffer.push_back(3); int value; CHECK_EQ(buffer.pop_back(&value), true); CHECK_EQ(value, 3); CHECK_EQ(buffer.size(), 2); CHECK_EQ(buffer.back(), 2); CHECK_EQ(buffer.pop_back(&value), true); CHECK_EQ(value, 2); CHECK_EQ(buffer.size(), 1); CHECK_EQ(buffer.front(), 1); CHECK_EQ(buffer.back(), 1); CHECK_EQ(buffer.pop_back(&value), true); CHECK_EQ(value, 1); CHECK(buffer.empty()); CHECK_EQ(buffer.pop_back(&value), false); } TEST_CASE("circular_buffer push_front operation") { CircularBuffer buffer(3); buffer.push_front(1); buffer.push_front(2); buffer.push_front(3); CHECK_EQ(buffer.size(), 3); CHECK_EQ(buffer.front(), 3); CHECK_EQ(buffer.back(), 1); buffer.push_front(4); CHECK_EQ(buffer.size(), 3); CHECK_EQ(buffer.front(), 4); CHECK_EQ(buffer.back(), 2); int value; CHECK_EQ(buffer.pop_back(&value), true); CHECK_EQ(value, 2); CHECK_EQ(buffer.pop_back(&value), true); CHECK_EQ(value, 3); CHECK_EQ(buffer.pop_back(&value), true); CHECK_EQ(value, 4); CHECK(buffer.empty()); } TEST_CASE("circular_buffer large data block operations") { CircularBuffer buffer(100); // Test adding a large block of data (10x buffer capacity) const size_t large_data_size = 1000; for (size_t i = 0; i < large_data_size; ++i) { buffer.push_back(static_cast(i)); } // Buffer should be full and contain the last 100 elements CHECK(buffer.full()); CHECK_EQ(buffer.size(), 100); CHECK_EQ(buffer.front(), static_cast(large_data_size - 100)); CHECK_EQ(buffer.back(), static_cast(large_data_size - 1)); // Verify all elements in buffer are correct (last 100 values) for (size_t i = 0; i < buffer.size(); ++i) { CHECK_EQ(buffer[i], static_cast(large_data_size - 100 + i)); } // Test popping all elements to ensure integrity int value; for (size_t i = 0; i < 100; ++i) { CHECK_EQ(buffer.pop_front(&value), true); CHECK_EQ(value, static_cast(large_data_size - 100 + i)); } CHECK(buffer.empty()); } TEST_CASE("circular_buffer stress test with rapid operations") { CircularBuffer buffer(50); // Stress test: rapid push/pop operations const size_t stress_iterations = 1000; // Reduced from 10000 size_t total_added = 0; size_t total_removed = 0; FL_UNUSED(total_added); FL_UNUSED(total_removed); for (size_t i = 0; i < stress_iterations; ++i) { // Add 3 elements buffer.push_back(static_cast(i * 3)); buffer.push_back(static_cast(i * 3 + 1)); buffer.push_back(static_cast(i * 3 + 2)); total_added += 3; // Remove 2 elements when possible if (buffer.size() >= 2) { int dummy; buffer.pop_front(&dummy); buffer.pop_front(&dummy); total_removed += 2; } // Verify buffer doesn't exceed capacity CHECK(buffer.size() <= 50); CHECK_FALSE(buffer.size() > buffer.capacity()); } // Verify the buffer is within expected bounds // Since we add 3 and remove 2 per iteration (when possible), // the buffer should have grown but be constrained by capacity CHECK(buffer.size() <= 50); CHECK_FALSE(buffer.empty()); // Additional verification: if we've done many iterations, // the buffer should be close to or at capacity if (stress_iterations >= 50) { CHECK(buffer.size() >= 45); // Should be near capacity } } TEST_CASE("circular_buffer wraparound integrity test") { CircularBuffer buffer(7); // Prime number for interesting wraparound behavior // Fill buffer multiple times to test wraparound const size_t cycles = 20; // Reduced from 100 for (size_t cycle = 0; cycle < cycles; ++cycle) { // Fill the buffer completely for (size_t i = 0; i < 7; ++i) { buffer.push_back(static_cast(cycle * 7 + i)); } // Verify buffer state after each fill CHECK(buffer.full()); CHECK_EQ(buffer.size(), 7); // Check that elements are in correct order for (size_t i = 0; i < 7; ++i) { CHECK_EQ(buffer[i], static_cast(cycle * 7 + i)); } // Empty the buffer partially (remove 3 elements) for (size_t i = 0; i < 3; ++i) { int value; CHECK_EQ(buffer.pop_front(&value), true); CHECK_EQ(value, static_cast(cycle * 7 + i)); } CHECK_EQ(buffer.size(), 4); } } TEST_CASE("circular_buffer bulk operations without overflow") { CircularBuffer buffer(1000); // Test 1: Add elements in chunks const size_t chunk_size = 250; const size_t num_chunks = 8; // Reduced from 20 - Total: 2000 elements (2x buffer capacity) for (size_t chunk = 0; chunk < num_chunks; ++chunk) { for (size_t i = 0; i < chunk_size; ++i) { buffer.push_back(static_cast(chunk * chunk_size + i)); } // Verify buffer constraints are maintained CHECK(buffer.size() <= 1000); CHECK_FALSE(buffer.size() > buffer.capacity()); } // Buffer should be full with the last 1000 elements CHECK(buffer.full()); CHECK_EQ(buffer.size(), 1000); // Verify the buffer contains the correct last 1000 elements size_t expected_start = (num_chunks * chunk_size) - 1000; // 1000 for (size_t i = 0; i < buffer.size(); ++i) { CHECK_EQ(buffer[i], static_cast(expected_start + i)); } // Test 2: Mixed operations (bulk add, partial remove) for (size_t round = 0; round < 20; ++round) { // Reduced from 100 // Add 50 elements for (size_t i = 0; i < 50; ++i) { buffer.push_back(static_cast(10000 + round * 50 + i)); } // Remove 30 elements for (size_t i = 0; i < 30; ++i) { int dummy; if (!buffer.empty()) { buffer.pop_front(&dummy); } } // Verify constraints CHECK(buffer.size() <= 1000); CHECK_FALSE(buffer.size() > buffer.capacity()); } } TEST_CASE("circular_buffer edge case with maximum indices") { CircularBuffer buffer(5); // Test that internal index calculations don't overflow // by simulating many wraparounds const size_t many_operations = 10000; // Reduced from 1000000 for (size_t i = 0; i < many_operations; ++i) { buffer.push_back(static_cast(i % 100)); // Occasionally pop elements to create varied states if (i % 7 == 0 && !buffer.empty()) { int dummy; buffer.pop_front(&dummy); } // Verify buffer integrity CHECK(buffer.size() <= 5); CHECK_FALSE(buffer.size() > buffer.capacity()); // Occasionally verify element access doesn't crash if (i % 1000 == 0 && !buffer.empty()) { volatile int test_front = buffer.front(); volatile int test_back = buffer.back(); for (size_t j = 0; j < buffer.size(); ++j) { volatile int test_indexed = buffer[j]; (void)test_front; (void)test_back; (void)test_indexed; } } } } TEST_CASE("circular_buffer memory safety with alternating operations") { CircularBuffer buffer(10); // Pattern that could potentially cause memory issues if buffer logic is wrong for (size_t iteration = 0; iteration < 100; ++iteration) { // Reduced from 1000 // Fill buffer for (size_t i = 0; i < 15; ++i) { // Overfill by 5 buffer.push_back(static_cast(iteration * 15 + i)); } // Verify buffer state CHECK(buffer.full()); CHECK_EQ(buffer.size(), 10); // Empty buffer completely while (!buffer.empty()) { int value; CHECK_EQ(buffer.pop_front(&value), true); } CHECK(buffer.empty()); CHECK_EQ(buffer.size(), 0); // Fill from back for (size_t i = 0; i < 12; ++i) { // Overfill by 2 buffer.push_front(static_cast(iteration * 12 + i)); } // Verify buffer state CHECK(buffer.full()); CHECK_EQ(buffer.size(), 10); // Empty from back while (!buffer.empty()) { int value; CHECK_EQ(buffer.pop_back(&value), true); } CHECK(buffer.empty()); } }