// SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2026 steredbits and Pelagia contributors // Tests of the bounded queue: limits, serial, abort (no blocking), flush. #include "player/bounded_queue.h" #include #include #include #include "test_framework.h" using player::BoundedQueue; static void test_limits() { BoundedQueue q(2, 1000); CHECK(q.try_push(1, 10, 0)); CHECK(q.try_push(2, 10, 0)); CHECK(!q.try_push(3, 10, 0)); // limit on the count BoundedQueue qb(100, 10); CHECK(qb.try_push(1, 6, 0)); CHECK(!qb.try_push(2, 6, 0)); // limit in bytes BoundedQueue::Entry e; CHECK(qb.pop(&e)); CHECK_EQ(e.item, 1); CHECK_EQ(e.bytes, 6u); CHECK(qb.try_push(2, 6, 0)); // An entry bigger than the limit passes if the queue is empty // (otherwise the pipeline would block forever). BoundedQueue qo(4, 10); CHECK(qo.try_push(1, 100, 0)); CHECK(!qo.try_push(2, 1, 0)); } static void test_serial_passthrough() { BoundedQueue q(4, 1000); q.try_push(42, 1, 7); BoundedQueue::Entry e; CHECK(q.pop(&e)); CHECK_EQ(e.item, 42); CHECK_EQ(e.serial, 7); } static void test_abort_unblocks_push() { BoundedQueue q(1, 1000); CHECK(q.try_push(1, 1, 0)); std::atomic returned{false}; std::atomic push_result{true}; std::thread t([&] { push_result = q.push(2, 1, 0); // file pleine : bloque returned = true; }); std::this_thread::sleep_for(std::chrono::milliseconds(50)); CHECK(!returned.load()); q.abort(); t.join(); CHECK(returned.load()); CHECK(!push_result.load()); } static void test_abort_unblocks_pop() { BoundedQueue q(4, 1000); std::atomic returned{false}; std::atomic pop_result{true}; std::thread t([&] { BoundedQueue::Entry e; pop_result = q.pop(&e); // file vide : bloque returned = true; }); std::this_thread::sleep_for(std::chrono::milliseconds(50)); CHECK(!returned.load()); q.abort(); t.join(); CHECK(returned.load()); CHECK(!pop_result.load()); } static void test_flush_releases_and_unblocks() { BoundedQueue q(2, 1000); q.try_push(1, 1, 0); q.try_push(2, 1, 0); std::atomic pushed{false}; std::thread t([&] { q.push(3, 1, 1); // blocks until the flush pushed = true; }); std::this_thread::sleep_for(std::chrono::milliseconds(50)); int released = 0; q.flush([&](BoundedQueue::Entry*) { ++released; }); t.join(); CHECK(pushed.load()); CHECK_EQ(released, 2); CHECK_EQ(q.size(), 1u); // the entry pushed after the flush } static void test_stress_producer_consumer() { BoundedQueue q(8, 1u << 20); const int kCount = 20000; long long sum = 0; std::thread producer([&] { for (int i = 0; i < kCount; ++i) { q.push(i, 4, 0); } q.push(-1, 0, 0); // fin }); std::thread consumer([&] { BoundedQueue::Entry e; while (q.pop(&e) && e.item != -1) { sum += e.item; } }); producer.join(); consumer.join(); CHECK_EQ(sum, static_cast(kCount) * (kCount - 1) / 2); CHECK_EQ(q.size(), 0u); } // Duration totals (reserve) and adjustable limits. static void test_duration_and_limits() { BoundedQueue q(2, 1000); CHECK(q.try_push(1, 1, 0, 40)); CHECK(q.try_push(2, 1, 0, 21)); CHECK_EQ(q.duration_ms(), 61); CHECK(!q.try_push(3, 1, 0, 40)); // limit on the count q.set_limits(10, 1000); // deeper queue (network) CHECK(q.try_push(3, 1, 0, 40)); CHECK_EQ(q.duration_ms(), 101); BoundedQueue::Entry e; CHECK(q.pop(&e)); CHECK_EQ(e.duration_ms, 40); CHECK_EQ(q.duration_ms(), 61); q.flush([](BoundedQueue::Entry*) {}); CHECK_EQ(q.duration_ms(), 0); CHECK(q.try_push(4, 1, 1)); // default duration: 0 CHECK_EQ(q.duration_ms(), 0); } int main() { test_limits(); test_duration_and_limits(); test_serial_passthrough(); test_abort_unblocks_push(); test_abort_unblocks_pop(); test_flush_releases_and_unblocks(); test_stress_producer_consumer(); return testfw::test_failures(); }