// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2026 Martino Ferrari #include "harness.hpp" #include PLOT_TEST(ring_wraps_and_keeps_latest) { plot::data::RingBuffer r(8); // capacity rounded up to a power of two for (int i = 0; i < 20; ++i) r.push(f32(i)); PLOT_CHECK(r.size() == 8); PLOT_CHECK_NEAR(r[0], 12.0); // oldest retained PLOT_CHECK_NEAR(r[7], 19.0); // newest } PLOT_TEST(ring_clear_resets) { plot::data::RingBuffer r(8); for (int i = 0; i < 5; ++i) r.push(f32(i)); r.clear(); PLOT_CHECK(r.size() == 0); r.push(42.0f); PLOT_CHECK(r.size() == 1); PLOT_CHECK_NEAR(r[0], 42.0); } PLOT_TEST(ring_clear_to_resumes_the_absolute_count) { // A recompute: a derived ring is emptied and refilled from the absolute origin its source // retains, so it has to come back numbered as the source is. `clear()` restarts at 0, which // leaves a refilled ring reporting a produced() a whole window behind an identically fed one. plot::data::RingBuffer src(8); plot::data::RingBuffer derived(8); for (int i = 0; i < 20; ++i) { src.push(f32(i)); derived.push(f32(i) * 2.0f); } PLOT_CHECK(src.base() == 12); derived.clear_to(src.base()); for (u64 i = 0; i < src.size(); ++i) derived.push(src[i] * 3.0f); // Numbered as the source: same produced(), same base(), and the retained windows pair up. PLOT_CHECK(derived.produced() == src.produced()); PLOT_CHECK(derived.base() == src.base()); PLOT_CHECK(derived.size() == src.size()); PLOT_CHECK_NEAR(derived[0], 36.0); // 12 * 3 PLOT_CHECK_NEAR(derived[7], 57.0); // 19 * 3 // And the absolute reader agrees with the windowed one, which is what a snapshot reads. f32 at = 0.0f; derived.copy_from(19, 1, &at); PLOT_CHECK_NEAR(at, 57.0); } PLOT_TEST(ring_clear_to_zero_is_clear) { plot::data::RingBuffer r(8); for (int i = 0; i < 5; ++i) r.push(f32(i)); r.clear_to(0); PLOT_CHECK(r.size() == 0); PLOT_CHECK(r.produced() == 0); r.push(42.0f); PLOT_CHECK_NEAR(r[0], 42.0); } PLOT_TEST(ring_capacity_rounds_up_to_a_power_of_two) { // The capacity is rounded up so slot addressing stays a mask, and capacity() // reports the rounded value -- the one callers must size for (R4). PLOT_CHECK(plot::data::RingBuffer(100).capacity() == 128); PLOT_CHECK(plot::data::RingBuffer(1).capacity() == 1); PLOT_CHECK(plot::data::RingBuffer(0).capacity() == 1); PLOT_CHECK(plot::data::RingBuffer(64).capacity() == 64); // already a power plot::data::RingBuffer r(100); for (int i = 0; i < 1000; ++i) r.push(f32(i)); PLOT_CHECK(r.size() == 128); // size() saturates at the rounded capacity PLOT_CHECK_NEAR(r[0], 872.0); // oldest retained PLOT_CHECK_NEAR(r[127], 999.0); // newest } PLOT_TEST(ring_amend_rewrites_a_retained_slot) { // Backfill: a slot pushed as a placeholder is rewritten once the data that belongs there // arrives. The index is absolute, because that is the only coordinate a late packet can // compute -- a retained-window index would have moved by the time it got here. plot::data::RingBuffer r(8); for (int i = 0; i < 6; ++i) r.push(f32(i)); PLOT_CHECK(r.amend(3, 42.0f)); PLOT_CHECK_NEAR(r[3], 42.0); PLOT_CHECK(r.produced() == 6); // an amend is not a push } PLOT_TEST(ring_amend_refuses_an_index_the_ring_no_longer_holds) { // The window slides on the producer, so a late amend may arrive after its slot was // overwritten. Writing it anyway would corrupt whatever sample now owns that slot, so the // bounds test is the whole safety of the operation and the caller reads the result. plot::data::RingBuffer r(8); for (int i = 0; i < 20; ++i) r.push(f32(i)); PLOT_CHECK(r.base() == 12); PLOT_CHECK(!r.amend(3, 42.0f)); // aged out PLOT_CHECK(!r.amend(20, 42.0f)); // never produced PLOT_CHECK_NEAR(r[0], 12.0); // and nothing moved PLOT_CHECK_NEAR(r[7], 19.0); }