Files
riojoy/tests/RioJoy.Core.Tests/TestPolyfills.cs
T
CydandClaude Fable 5 47df14cc61 RioSerialLink: stop-and-wait command delivery (supersedes NAK-race resend)
Bench falsified the v1 retransmit (testlogs/riomash-patched-62500-retx):
resends tracked NAKs 1:1 (321/321) yet lamps still stuck/missed — under
mash bursts the NAK arrives after a newer command is already "latest",
so the wrong packet was resent; and total shreds never NAK at all.

Now commands are stop-and-wait: ONE in flight (_commandGate), resolved
by ACK, NAK, or AckTimeout (50ms); NAK/timeout retransmits THE SAME
packet up to CommandRetransmitLimit (2), then drops (idempotent - the
next state update supersedes). Attribution is exact by construction and
timeouts catch silent shreds. Control-byte replies bypass the gate so
board traffic is never delayed; a request's own reply (analog/version/
check) also resolves the wait, so request/reply exchanges never burn
the timeout even if the board sends no explicit ACK.

7 tests (same-packet resend, timeout retry+drop, ACK completion,
serialization, reply-resolves-request, stray-NAK no-op, disable);
282 green. Mash summary now reports NAK/timeout resends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 13:20:30 -05:00

33 lines
1.3 KiB
C#

namespace RioJoy.Core.Tests;
/// <summary>
/// net48 test-only polyfills. Visible to all tests via enclosing-namespace scope.
/// </summary>
internal static class TaskTestExtensions
{
/// <summary>
/// Polyfill for Task&lt;T&gt;.WaitAsync(TimeSpan) (net6+), absent on net48.
/// Throws <see cref="TimeoutException"/> if the task does not complete in time.
/// </summary>
public static async Task<T> WaitAsync<T>(this Task<T> task, TimeSpan timeout)
{
using var cts = new CancellationTokenSource();
Task completed = await Task.WhenAny(task, Task.Delay(timeout, cts.Token)).ConfigureAwait(false);
if (completed != task)
throw new TimeoutException($"Task did not complete within {timeout}.");
cts.Cancel(); // stop the delay timer
return await task.ConfigureAwait(false);
}
/// <summary>Non-generic counterpart of <see cref="WaitAsync{T}"/>.</summary>
public static async Task WaitAsync(this Task task, TimeSpan timeout)
{
using var cts = new CancellationTokenSource();
Task completed = await Task.WhenAny(task, Task.Delay(timeout, cts.Token)).ConfigureAwait(false);
if (completed != task)
throw new TimeoutException($"Task did not complete within {timeout}.");
cts.Cancel();
await task.ConfigureAwait(false);
}
}