// In-process unit test for the shared audio ring (coop/audio_ring.hpp). No // shared memory, no hook, no audio device: it exercises the lock-free SPSC // push/pop, wrap-around, the format handshake, and the overrun/drop policy in a // single process. Exits 0 on pass, 1 on failure. #include #include #include #include #include #include "coop/audio_ring.hpp" using namespace coop; namespace { int g_failures = 0; void check(bool ok, const char* what) { if (!ok) { std::printf(" FAIL: %s\n", what); ++g_failures; } } // Allocate a ring of `capacity` data bytes in a plain heap buffer (placement-new // the header so its atomics are constructed) and return it ready to use. AudioRingHeader* make_ring(std::vector& storage, std::uint32_t capacity) { storage.assign(audio_ring_total_size(capacity), 0); auto* h = new (storage.data()) AudioRingHeader(); audio_ring_init(*h, capacity); return h; } } // namespace int main() { // --- Validity + format handshake. --- { std::vector storage; AudioRingHeader* h = make_ring(storage, 4096); check(audio_ring_valid(*h), "freshly inited ring is valid"); check(!audio_ring_format_ready(*h), "format not ready before set"); audio_ring_set_format(*h, 48000, 2, 32, 3 /*IEEE_FLOAT*/, 8); check(audio_ring_format_ready(*h), "format ready after set"); check(h->sample_rate == 48000 && h->channels == 2 && h->bits == 32 && h->format_tag == 3 && h->block_align == 8, "format fields round-trip"); } // --- Basic push/pop integrity. --- { std::vector storage; AudioRingHeader* h = make_ring(storage, 4096); std::uint8_t src[256]; for (int i = 0; i < 256; ++i) { src[i] = static_cast(i); } check(audio_ring_push(*h, src, sizeof(src), 32), "push 256 bytes"); check(audio_ring_available(*h) == sizeof(src), "available == pushed"); check(h->frames_produced.load() == 32, "frames_produced tracked"); std::uint8_t dst[256] = {}; check(audio_ring_pop(*h, dst, sizeof(dst)) == sizeof(src), "pop returns all bytes"); check(std::memcmp(src, dst, sizeof(src)) == 0, "popped bytes match pushed"); check(audio_ring_available(*h) == 0, "ring empty after drain"); } // --- Wrap-around: keep pushing/popping past capacity to force a split copy. --- { const std::uint32_t cap = 1024; std::vector storage; AudioRingHeader* h = make_ring(storage, cap); std::uint8_t counter = 0; std::uint8_t expect = 0; const std::uint32_t chunk = 300; // not a divisor of cap, so offsets drift across the seam for (int iter = 0; iter < 50; ++iter) { std::uint8_t buf[300]; for (std::uint32_t i = 0; i < chunk; ++i) { buf[i] = counter++; } check(audio_ring_push(*h, buf, chunk, chunk), "wrap push fits"); std::uint8_t out[300] = {}; check(audio_ring_pop(*h, out, chunk) == chunk, "wrap pop full chunk"); bool ok = true; for (std::uint32_t i = 0; i < chunk; ++i) { ok = ok && out[i] == expect++; } check(ok, "wrap data integrity across the ring seam"); } } // --- Overrun: a packet that doesn't fit is dropped whole, bumping overruns. --- { const std::uint32_t cap = 512; std::vector storage; AudioRingHeader* h = make_ring(storage, cap); std::vector half(cap / 2, 0xAB); check(audio_ring_push(*h, half.data(), cap / 2, 1), "first half fits"); check(audio_ring_push(*h, half.data(), cap / 2, 1), "second half fills ring"); check(audio_ring_available(*h) == cap, "ring full"); std::vector more(16, 0xCD); check(!audio_ring_push(*h, more.data(), 16, 1), "push into full ring rejected"); check(h->overruns.load() == 1, "overrun counted"); check(audio_ring_available(*h) == cap, "rejected push left ring untouched"); // Draining makes room again; subsequent pushes succeed. std::vector drain(cap, 0); check(audio_ring_pop(*h, drain.data(), cap) == cap, "drain full ring"); check(audio_ring_push(*h, more.data(), 16, 1), "push succeeds after drain"); check(h->overruns.load() == 1, "overruns not bumped on success"); } std::printf(g_failures == 0 ? "AUDIO RING TEST PASS\n" : "AUDIO RING TEST FAILED (%d)\n", g_failures); return g_failures == 0 ? 0 : 1; }