From b57d5ed6451e3cf66e53f45831ba8cc4c54c5da8 Mon Sep 17 00:00:00 2001 From: Dietmar Borgards <2646931+dborgards@users.noreply.github.com> Date: Fri, 11 Sep 2026 01:08:34 +0200 Subject: [PATCH] test(isotp): dispose the channel and the receive CTS deterministically Clears the three open code-scanning alerts on `main`, all in IsoTpChannelIntegrationTests and all the same class of test hygiene: #274, #275 cs/dispose-not-called-on-throw (lines 417, 418) #276 cs/local-not-disposed (line 798) The channel in the idempotent-dispose test was created with a plain `var` and closed only by the two explicit Dispose calls the test makes on purpose, so a ReceiveAsync that threw would leave it open for the rest of the run. It now also has a `using`; since the behaviour under test is that Dispose is idempotent, the third call at scope exit is by definition a no-op. The CancellationTokenSource at line 798 was constructed inline purely to reach `.Token`, leaving its timer to the finalizer. It is now owned by the test. Only the three reported sites are touched. The same inline-CTS spelling appears 10 more times in this file and about 65 times across the suite; sweeping those is a separate decision, not something to smuggle into a fix for three specific alerts. Verified: 412/412 on net10.0, and the net48 leg compiles with 0 warnings. Co-Authored-By: Claude Opus 5 --- .../TestCases/IsoTp/IsoTpChannelIntegrationTests.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/CanKit.Pro.Tests/TestCases/IsoTp/IsoTpChannelIntegrationTests.cs b/tests/CanKit.Pro.Tests/TestCases/IsoTp/IsoTpChannelIntegrationTests.cs index 895960a..c74e338 100644 --- a/tests/CanKit.Pro.Tests/TestCases/IsoTp/IsoTpChannelIntegrationTests.cs +++ b/tests/CanKit.Pro.Tests/TestCases/IsoTp/IsoTpChannelIntegrationTests.cs @@ -411,7 +411,11 @@ public async Task Dispose_Unblocks_Pending_ReceiveAsync() using var busA = OpenClassic(session, 0); using var busB = OpenClassic(session, 1); - var channel = IsoTpFactory.Open(busA, IsoTpEndpoint.Normal(0x123, 0x321), FastOptions()); + // `using` on top of the two explicit calls below: the point of this test is that Dispose + // is idempotent, so a third call at scope exit changes nothing -- but without it a + // ReceiveAsync that throws would leave the channel open for the rest of the run + // (CodeQL cs/dispose-not-called-on-throw). + using var channel = IsoTpFactory.Open(busA, IsoTpEndpoint.Normal(0x123, 0x321), FastOptions()); var recvTask = channel.ReceiveAsync(); // Idempotent dispose channel.Dispose(); @@ -795,7 +799,10 @@ public async Task Send_Faults_With_The_Bus_Layer_Exception_And_Channel_Remains_U "the failure the bus layer reported must reach the caller unrewritten"); // Gate free and _tx cleared: the very next send goes through end to end. - var recvTask = receiver.ReceiveAsync(new CancellationTokenSource(ShortTimeout).Token); + // Owned rather than inline, so the timer it holds is released when the test ends and + // not when the finalizer gets round to it (CodeQL cs/local-not-disposed). + using var recvCts = new CancellationTokenSource(ShortTimeout); + var recvTask = receiver.ReceiveAsync(recvCts.Token); byte[] normal = { 0x11, 0x22, 0x33 }; await sender.SendAsync(normal).WaitAsync(ShortTimeout); (await recvTask).Should().Equal(normal);