Tests: audio-correctness + infra pinning, group 1 (review Phase 2)

Five subsystems that had ZERO coverage - all pure code where a real bug would ship
green because the flow tests only prove packets move, not that bytes are right:
- AudioRingBuffer: wrap correctness, overflow drops OLDEST, DropOldest/TrimFromProducer
  semantics, silence-fill + exact underrun/drop counters, oversized-write degrade.
- PcmPack 24-bit codec: +/-1-LSB round-trip incl. negatives (a sign-extension bug =
  distortion on the whole PCM path), sign preservation, over-range clamps not wraps.
- RemSoundUpdater tag filtering: server-v* relay tags rejected, numeric ordering
  (v5.10 > v5.9), suffix tolerance - what stops a wrong-version auto-update.
- RouterPortMapper.IsCgnatAddress: RFC 6598 boundaries exact (made internal for test).
- NetworkPriority.TryAttach: no-throw smoke on a bound socket.

Gate 52/52.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-07-23 15:51:57 +01:00
co-authored by Claude Fable 5
parent 527c2976c9
commit 9627f320ca
2 changed files with 149 additions and 1 deletions
+1 -1
View File
@@ -321,7 +321,7 @@ internal sealed class RouterPortMapper : IDisposable
}
}
private static bool IsCgnatAddress(IPAddress addr)
internal static bool IsCgnatAddress(IPAddress addr) // internal for the self-test (RFC 6598 range pinning)
{
if (addr.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) return false;
var b = addr.GetAddressBytes();
+148
View File
@@ -56,6 +56,11 @@ internal static class SelfTest
RunStep(results, "Encryption round-trip", Encryption);
RunStep(results, "Packet framing and rejection", PacketFraming);
RunStep(results, "Server wire-format compatibility", ServerWireCompat);
RunStep(results, "Audio ring buffer integrity (wrap, drop-oldest, underrun fill)", RingBufferIntegrity);
RunStep(results, "24-bit PCM pack/unpack (sign, clamp, 1-LSB accuracy)", Pcm24RoundTrip);
RunStep(results, "Updater tag filtering (client vs server releases, version order)", UpdaterTagFiltering);
RunStep(results, "CGNAT range detection (RFC 6598)", CgnatDetection);
RunStep(results, "Network QoS attach (no throw on a bound socket)", NetworkPrioritySmoke);
RunStep(results, "App settings save and reload", SettingsRoundTrip);
RunStep(results, "Per-peer shaping DSP", PeerShapingDsp);
RunStep(results, "Multi-output fan-out (both lanes)", FanOutToBothOutputs);
@@ -946,6 +951,149 @@ internal static class SelfTest
return "follower flagged + sentinel shared with the app; service resolves it to the live default render endpoint";
}
/// <summary>The SPSC ring buffer is the heart of every audio path (capture mix + playout), and until
/// now nothing asserted its BYTES were right — only that packets flowed. Pins: wrap-around
/// correctness, overflow dropping the OLDEST bytes, trim/drop semantics, silence-fill on underrun,
/// and exact drop/underrun counters.</summary>
private static string? RingBufferIntegrity()
{
var ring = new AudioRingBuffer(100);
Check(ring.CapacityBytes == 128, $"capacity must round up to a power of two (got {ring.CapacityBytes})");
// Two passes: the second crosses the physical wrap point of the 128-byte ring.
var src = new byte[100];
for (var i = 0; i < src.Length; i++) src[i] = (byte)i;
var dst = new byte[100];
for (var pass = 0; pass < 2; pass++)
{
ring.Write(src);
Check(ring.BufferedBytes == 100, "buffered count must match what was written");
var got = ring.Read(dst);
Check(got == 100 && dst.AsSpan().SequenceEqual(src), $"pass {pass}: bytes must read back exact and in order across the wrap");
}
Check(ring.DropCount == 0 && ring.UnderrunCount == 0, "clean traffic must not count drops or underruns");
// Overflow drops the OLDEST: fill with A, push 64 B — read must be the surviving A then the B.
var a = new byte[128]; Array.Fill(a, (byte)'A');
var b = new byte[64]; Array.Fill(b, (byte)'B');
ring.Write(a);
ring.Write(b);
var full = new byte[128];
ring.Read(full);
Check(full.AsSpan(0, 64).IndexOfAnyExcept((byte)'A') < 0 && full.AsSpan(64).IndexOfAnyExcept((byte)'B') < 0,
"overflow must drop the oldest bytes and keep arrival order");
Check(ring.DropCount == 64, $"the 64 displaced bytes must count as drops (got {ring.DropCount})");
// Underrun: shortfall is silence-filled and counted once; return value is only the real bytes.
var few = new byte[10]; Array.Fill(few, (byte)7);
ring.Write(few);
var want20 = new byte[20]; Array.Fill(want20, (byte)0xEE);
var read = ring.Read(want20);
Check(read == 10, "the return value must be only the real bytes");
Check(want20.AsSpan(0, 10).IndexOfAnyExcept((byte)7) < 0 && want20.AsSpan(10).IndexOfAnyExcept((byte)0) < 0,
"the shortfall must be filled with silence (zeros)");
Check(ring.UnderrunCount == 1, $"the shortfall must count exactly one underrun (got {ring.UnderrunCount})");
// DropOldest (consumer side) and TrimFromProducer (producer side) both discard the OLDEST bytes.
ring.Write(src);
ring.DropOldest(5);
var after = new byte[95];
ring.Read(after);
Check(after[0] == 5 && after[94] == 99, "DropOldest must discard from the head (oldest)");
ring.Write(src);
var trimmed = ring.TrimFromProducer(30);
Check(trimmed == 70 && ring.BufferedBytes == 30, "TrimFromProducer must report and keep exact counts");
var last30 = new byte[30];
ring.Read(last30);
Check(last30[0] == 70 && last30[29] == 99, "the trim must keep the NEWEST bytes");
// A write bigger than the whole ring keeps only the newest capacity's worth (documented degrade).
var big = new byte[300];
for (var i = 0; i < big.Length; i++) big[i] = (byte)(i & 0xFF);
ring.Write(big);
Check(ring.BufferedBytes == 128, "an oversized write must degrade to the newest capacity's worth");
var tail128 = new byte[128];
ring.Read(tail128);
Check(tail128.AsSpan().SequenceEqual(big.AsSpan(300 - 128)), "the oversized write must keep the NEWEST bytes");
return "wrap-correct; overflow/trim drop oldest; underrun silence-fills; counters exact";
}
/// <summary>The 24-bit wire codec: a sign-extension bug here distorts every PCM stream while packets
/// still flow, so the flow tests can't see it. Pins ±1-LSB round-trip accuracy including negatives,
/// sign preservation, and clamping (never wrapping) of over-range samples.</summary>
private static string? Pcm24RoundTrip()
{
var lsb = 1f / 8388607f;
var src = new[] { -1f, -0.5f, -0.001f, -2 * lsb, 0f, 2 * lsb, 0.001f, 0.25f, 0.5f, 0.99999f, 1f };
var bytes = new byte[src.Length * 3];
PcmPack.FloatToInt24LE(src, bytes);
var back = new float[src.Length];
PcmPack.Int24LEToFloat(bytes, back);
for (var i = 0; i < src.Length; i++)
{
var err = Math.Abs(back[i] - src[i]);
Check(err <= 1.5f * lsb, $"sample {src[i]} must round-trip within ~1 LSB (error {err})");
Check(Math.Sign(back[i]) == Math.Sign(src[i]) || Math.Abs(src[i]) <= lsb,
$"the SIGN of {src[i]} must survive the pack/unpack");
}
var loud = new[] { 2f, -2f };
var loudBytes = new byte[6];
PcmPack.FloatToInt24LE(loud, loudBytes);
var loudBack = new float[2];
PcmPack.Int24LEToFloat(loudBytes, loudBack);
Check(Math.Abs(loudBack[0] - 1f) < 1e-6f && Math.Abs(loudBack[1] + 1f) < 1e-6f,
"over-range samples must CLAMP to ±1, never wrap");
return "±1-LSB accuracy incl. negatives; sign preserved; over-range clamps";
}
/// <summary>The updater lists ALL GitHub releases in a repo that also hosts the relay's server-v*
/// releases. Tag filtering and NUMERIC version ordering are what stop a wrong-version (or downgrade)
/// auto-update; neither had a test.</summary>
private static string? UpdaterTagFiltering()
{
Check(!RemSoundUpdater.IsClientReleaseTag("server-v2.4"), "relay tags must be rejected (same repo)");
Check(!RemSoundUpdater.IsClientReleaseTag(null) && !RemSoundUpdater.IsClientReleaseTag("")
&& !RemSoundUpdater.IsClientReleaseTag("latest"), "junk tags must be rejected");
Check(RemSoundUpdater.IsClientReleaseTag("v1.6") && RemSoundUpdater.IsClientReleaseTag("5.4")
&& RemSoundUpdater.IsClientReleaseTag("V5.10.2"), "client tags must be accepted");
Check(RemSoundUpdater.ParseTag("v5.10") > RemSoundUpdater.ParseTag("v5.9"),
"v5.10 must order ABOVE v5.9 (numeric, not string, comparison)");
Check(RemSoundUpdater.ParseTag("v5.4") == new Version(5, 4, 0), "two-part tags must fill the third part with 0");
Check(RemSoundUpdater.ParseTag("v5.4.1-beta+x") == new Version(5, 4, 1), "suffixes must not break parsing");
Check(RemSoundUpdater.ParseTag("server-v2.3") == new Version(0, 0, 3),
"a relay tag mis-parses as 0.0.3 — pinned as documentation of WHY IsClientReleaseTag must filter first");
return "server-v* rejected, client tags accepted, v5.10 > v5.9, suffixes tolerated";
}
/// <summary>The CGNAT check decides whether UPnP success is reported as 'mapped' or 'mapped but
/// unreachable from the internet'. Pins the RFC 6598 boundaries exactly.</summary>
private static string? CgnatDetection()
{
Check(RouterPortMapper.IsCgnatAddress(IPAddress.Parse("100.64.0.1")), "bottom of 100.64.0.0/10 must detect");
Check(RouterPortMapper.IsCgnatAddress(IPAddress.Parse("100.127.255.254")), "top of the range must detect");
Check(!RouterPortMapper.IsCgnatAddress(IPAddress.Parse("100.63.255.255")), "just below the range must not");
Check(!RouterPortMapper.IsCgnatAddress(IPAddress.Parse("100.128.0.1")), "just above the range must not");
Check(!RouterPortMapper.IsCgnatAddress(IPAddress.Parse("192.168.1.1")), "private LAN space must not");
Check(!RouterPortMapper.IsCgnatAddress(IPAddress.Parse("2001:db8::1")), "IPv6 is out of scope");
return "RFC 6598 boundaries exact; private/IPv6 excluded";
}
/// <summary>QoS attach runs on every sender start; it must never throw on a healthy bound socket —
/// whether prioritisation is actually granted varies by machine and either answer is fine.</summary>
private static string? NetworkPrioritySmoke()
{
using var s = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork,
System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
s.Bind(new IPEndPoint(IPAddress.Loopback, 0));
var np = new NetworkPriority();
try
{
var attached = np.TryAttach(s, _ => { });
return $"no throw; attach reported {attached} (either is acceptable — qWave availability varies)";
}
finally { (np as IDisposable)?.Dispose(); }
}
/// <summary>The no-UAC restart used after a service-profile save (TryRestartNoAdmin) must FAIL SAFE:
/// against a service that doesn't exist it returns false, promptly, and never throws — that false is
/// what routes the caller onto the elevated fallback. (The success path needs the real installed