Observed over four CI runs of #91 — a branch containing no .cs changes at all, so none of these are caused by the code under test changing. Each failed exactly once and passed on a later run of the same or newer commit.
The cost is not the individual failure. It is that a green pull request can go red on the next run without anything changing, which makes "is CI red because of my change?" a question nobody can answer by looking.
Two of them are tolerance problems
J1939 measures the gap between periodic emissions with Stopwatch and asserts it sits within 30 % of a 600 ms period. 196.9 ms is over that. Worth noting before anyone reaches for a wider tolerance: the drift hypothesis the test guards against lands at half a period (~300 ms) off-grid, so widening to, say, 40 % would not blind it. But a tolerance picked to survive the slowest runner ever observed is a number that gets widened again the next time, and the test would stop meaning anything long before it stops failing.
ISO-TP measures CF-to-CF spacing against an advertised STmin of 5 ms with FastOptions(), and the receiver's N_Cr expired. Its own README already concedes the limit: "the effective CF spacing is STmin + OS scheduling latency … with no hard real-time guarantee under load". The test asserts a property the code cannot promise on a shared runner.
For both, the durable fix is to stop measuring wall-clock time: drive the actor's DeadlineScheduler from a virtual clock the test advances, so pacing and timeout behaviour are asserted deterministically. The real-time behaviour that genuinely depends on the host stays worth checking, but as a soft, non-gating observation rather than a build-breaking assertion.
The third one may not be a test problem at all
TimedOut_Request_Does_Not_Poison_Next_Same_Service_Transaction sets P2ClientMax = 80 ms and has the ECU double Thread.Sleep(250) before replying. A 170 ms margin is not tight, so "the runner was slow" does not explain it on its own — the response cannot arrive early.
What can produce the observed result is the opposite of slowness at the point that matters: if the actor loop is starved for longer than 250 ms, then by the time it runs again both the expired deadline and the response are waiting, and the response wins the race. The client returns data instead of throwing.
If that reading is right, the test caught something real: a deadline that has already expired losing to a response that arrived afterwards. That is the same shape as #24, where an expired pending send stays in the FIFO and swallows the next echo, and it is what the L2 deadline primitive was introduced to prevent — an expiry that is guaranteed to be checked.
This is a hypothesis from one log, not a diagnosis. It should be reproduced under deliberate actor starvation before anyone decides which side the bug is on. Doing that first also decides the fix: if the client is right and the test is racy, it joins the two above; if the client is wrong, widening a tolerance would have hidden a defect.
Suggested order
- Reproduce the UDS case under forced actor starvation, and settle whether the client or the test is at fault. This one first, because it is the only one that might be a product bug.
- Move the ISO-TP and J1939 assertions onto a virtual clock.
- Only then consider what, if anything, should still be measured against the wall clock, and make that non-gating.
Related: #52 covers the missing normative negative tests — a different gap in the same suite. This issue is about tests that exist and cannot be trusted.
Observed over four CI runs of #91 — a branch containing no
.cschanges at all, so none of these are caused by the code under test changing. Each failed exactly once and passed on a later run of the same or newer commit.IsoTpStminTimingTests.Sender_Paces_Consecutive_Frames_According_To_Peer_Stminff7012aIsoTpTimeoutException: N_Cr timer expired waiting for next Consecutive FrameJ1939NodeTests.StartPeriodicSend_MultiFrame_KeepsFixedRate_Without_SendTime_Driftff7012aUdsClientTests.TimedOut_Request_Does_Not_Poison_Next_Same_Service_Transaction0a8812dExpected a <UdsTimeoutException> to be thrown, but no exception was thrownThe cost is not the individual failure. It is that a green pull request can go red on the next run without anything changing, which makes "is CI red because of my change?" a question nobody can answer by looking.
Two of them are tolerance problems
J1939 measures the gap between periodic emissions with
Stopwatchand asserts it sits within 30 % of a 600 ms period. 196.9 ms is over that. Worth noting before anyone reaches for a wider tolerance: the drift hypothesis the test guards against lands at half a period (~300 ms) off-grid, so widening to, say, 40 % would not blind it. But a tolerance picked to survive the slowest runner ever observed is a number that gets widened again the next time, and the test would stop meaning anything long before it stops failing.ISO-TP measures CF-to-CF spacing against an advertised STmin of 5 ms with
FastOptions(), and the receiver's N_Cr expired. Its own README already concedes the limit: "the effective CF spacing is STmin + OS scheduling latency … with no hard real-time guarantee under load". The test asserts a property the code cannot promise on a shared runner.For both, the durable fix is to stop measuring wall-clock time: drive the actor's
DeadlineSchedulerfrom a virtual clock the test advances, so pacing and timeout behaviour are asserted deterministically. The real-time behaviour that genuinely depends on the host stays worth checking, but as a soft, non-gating observation rather than a build-breaking assertion.The third one may not be a test problem at all
TimedOut_Request_Does_Not_Poison_Next_Same_Service_TransactionsetsP2ClientMax = 80 msand has the ECU doubleThread.Sleep(250)before replying. A 170 ms margin is not tight, so "the runner was slow" does not explain it on its own — the response cannot arrive early.What can produce the observed result is the opposite of slowness at the point that matters: if the actor loop is starved for longer than 250 ms, then by the time it runs again both the expired deadline and the response are waiting, and the response wins the race. The client returns data instead of throwing.
If that reading is right, the test caught something real: a deadline that has already expired losing to a response that arrived afterwards. That is the same shape as #24, where an expired pending send stays in the FIFO and swallows the next echo, and it is what the L2 deadline primitive was introduced to prevent — an expiry that is guaranteed to be checked.
This is a hypothesis from one log, not a diagnosis. It should be reproduced under deliberate actor starvation before anyone decides which side the bug is on. Doing that first also decides the fix: if the client is right and the test is racy, it joins the two above; if the client is wrong, widening a tolerance would have hidden a defect.
Suggested order
Related: #52 covers the missing normative negative tests — a different gap in the same suite. This issue is about tests that exist and cannot be trusted.