Files
CoopAllTheThings/tests/audio_ring_test.cpp
BlackMark 30eccf749d Apply clang-format across the whole tree
Run clang-format (the repo's .clang-format: LLVM base, 120 cols, tabs,
Allman functions) over every source file so the tree is formatter-clean.
Whitespace only -- no behavior change; full x64 + x86 suites pass.

Also set SortIncludes: false in .clang-format. Windows include order is
load-bearing (windows.h must precede tlhelp32.h / mmreg.h / xinput.h /
dinput.h; winsock2.h must precede windows.h), and the default
alphabetical sort reorders tlhelp32.h ahead of windows.h -- a build
break. Leaving order alone keeps the manual, correct grouping.
2026-07-12 11:52:53 +02:00

164 lines
6.2 KiB
C++

// 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 <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#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<std::uint8_t>& 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<std::uint8_t> 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<std::uint8_t> storage;
AudioRingHeader* h = make_ring(storage, 4096);
std::uint8_t src[256];
for (int i = 0; i < 256; ++i) {
src[i] = static_cast<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t> storage;
AudioRingHeader* h = make_ring(storage, cap);
std::vector<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> 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");
}
// --- Seam interaction: with the write head near the end, a wrapping push fits while an
// over-capacity wrapping push is dropped whole (the overrun + wrap split happening together). ---
{
const std::uint32_t cap = 512;
std::vector<std::uint8_t> storage;
AudioRingHeader* h = make_ring(storage, cap);
// Advance the write head near the end so subsequent writes wrap the buffer seam.
std::vector<std::uint8_t> pre(400, 0x55);
check(audio_ring_push(*h, pre.data(), 400, 1), "seam: prime to advance the write head");
std::vector<std::uint8_t> sink(400, 0);
check(audio_ring_pop(*h, sink.data(), 400) == 400, "seam: drain it (head now near the end)");
check(audio_ring_available(*h) == 0, "seam: empty again, write head near the seam");
// A 200-byte push now splits across the seam; it fits, so the data must survive the split.
std::vector<std::uint8_t> wrap(200);
for (std::uint32_t i = 0; i < 200; ++i) {
wrap[i] = static_cast<std::uint8_t>(i);
}
check(audio_ring_push(*h, wrap.data(), 200, 1), "seam: a wrapping push that fits is accepted");
check(audio_ring_available(*h) == 200, "seam: 200 bytes present after the wrapping push");
// A 400-byte push would also wrap but can't fit (free = 312) -> dropped whole, overruns++.
const std::uint64_t before = h->overruns.load();
std::vector<std::uint8_t> big(400, 0xEE);
check(!audio_ring_push(*h, big.data(), 400, 1), "seam: an over-capacity wrapping push is rejected whole");
check(h->overruns.load() == before + 1, "seam: overrun counted");
check(audio_ring_available(*h) == 200, "seam: rejected wrapping push left the ring untouched");
// Pop the wrapped payload back and verify integrity across the seam.
std::vector<std::uint8_t> out(200, 0);
check(audio_ring_pop(*h, out.data(), 200) == 200, "seam: pop the wrapped payload");
bool ok = true;
for (std::uint32_t i = 0; i < 200; ++i) {
ok = ok && out[i] == static_cast<std::uint8_t>(i);
}
check(ok, "seam: wrapping push/pop preserved data across the buffer seam");
}
std::printf(g_failures == 0 ? "AUDIO RING TEST PASS\n" : "AUDIO RING TEST FAILED (%d)\n", g_failures);
return g_failures == 0 ? 0 : 1;
}