From f341ab5c7548fb7dbb80e888e393ad78540b8c61 Mon Sep 17 00:00:00 2001 From: Mike Curn Date: Thu, 19 Mar 2026 10:50:51 -0700 Subject: [PATCH] Fix async finally block bug + add unit tests for bug (and two unit tests for happy-path execution with both regular and async finally blocks) --- .../TestCaseTests/FinallyExecutionTests.cs | 113 ++++++++++++++++++ .../TestData/Dependencies/SimulatorClasses.cs | 9 ++ .../TestBuilder.cs | 2 +- 3 files changed, 123 insertions(+), 1 deletion(-) diff --git a/IntelliTect.TestTools.TestFramework.Tests/TestCaseTests/FinallyExecutionTests.cs b/IntelliTect.TestTools.TestFramework.Tests/TestCaseTests/FinallyExecutionTests.cs index 469d85a..45196d4 100644 --- a/IntelliTect.TestTools.TestFramework.Tests/TestCaseTests/FinallyExecutionTests.cs +++ b/IntelliTect.TestTools.TestFramework.Tests/TestCaseTests/FinallyExecutionTests.cs @@ -8,6 +8,22 @@ namespace IntelliTect.TestTools.TestFramework.Tests.TestCaseTests { public class FinallyExecutionTests { + [Fact] + public async Task NoExceptionsWhenAllBlocksAndFinallyBlocksPass() + { + // Arrange + TestCase tc = new TestBuilder() + .AddDependencyInstance(true) + .AddTestBlock() + .AddFinallyBlock(true) + .Build(); + + // Act + await tc.ExecuteAsync(); + + // Assert + Assert.True(tc.Passed, "Test case did not get marked as Passed when we expected it."); + } [Fact] public async Task FinallyBlockThrowsExpectedExceptionWhenNotOverridingDefaultFinallyBehavior() @@ -88,5 +104,102 @@ public async Task OnlyTestBlockThrowsExpectedExceptionWhenOverridingDefaultFinal // Assert Assert.False(tc.Passed, "Test case did not get marked as Failed when we expected it."); } + + [Fact] + public async Task NoExceptionsWhenAllBlocksAndAsyncFinallyBlocksPass() + { + // Arrange + TestCase tc = new TestBuilder() + .AddDependencyInstance(true) + .AddTestBlock() + .AddAsyncFinallyBlock(true) + .Build(); + + // Act + await tc.ExecuteAsync(); + + // Assert + Assert.True(tc.Passed, "Test case did not get marked as Passed when we expected it."); + } + + [Fact] + public async Task AsyncFinallyBlockThrowsExpectedExceptionWhenNotOverridingDefaultFinallyBehavior() + { + // Arrange + TestCase tc = new TestBuilder() + .AddDependencyInstance(true) + .AddTestBlock() + .AddAsyncFinallyBlock() + .Build(); + + // Act + var ex = await Assert.ThrowsAsync(() => tc.ExecuteAsync()); + + // Assert + Assert.NotNull(ex.InnerExceptions); + Assert.Single(ex.InnerExceptions); + Assert.Contains("Test case succeeded", + ex.Message, + StringComparison.InvariantCultureIgnoreCase); + Assert.True(tc.Passed, "Test case did not get marked as Passed when we expected it."); + } + + [Fact] + public async Task TestBlockAndAsyncFinallyBlockThrowsExpectedExceptionWhenNotOverridingDefaultFinallyBehavior() + { + // Arrange + TestCase tc = new TestBuilder() + .AddDependencyInstance(false) + .AddTestBlock() + .AddAsyncFinallyBlock() + .Build(); + + // Act + var ex = await Assert.ThrowsAsync(() => tc.ExecuteAsync()); + + // Assert + Assert.NotNull(ex.InnerExceptions); + Assert.Equal(2, ex.InnerExceptions.Count); + Assert.Contains("Test case failed and finally blocks failed", + ex.Message, + StringComparison.InvariantCultureIgnoreCase); + Assert.False(tc.Passed, "Test case did not get marked as Failed when we expected it."); + } + + [Fact] + public async Task AsyncFinallyBlockDoesNotThrowExceptionWhenOverridingDefaultFinallyBehavior() + { + // Arrange + TestCase tc = new TestBuilder() + .AddDependencyInstance(true) + .AddTestBlock() + .AddAsyncFinallyBlock() + .Build(); + tc.ThrowOnFinallyBlockException = false; + + // Act + await tc.ExecuteAsync(); + + // Assert + Assert.True(tc.Passed, "Test case did not get marked as Passed when we expected it."); + } + + [Fact] + public async Task OnlyTestBlockThrowsExpectedExceptionWhenOverridingDefaultFinallyBehaviorWithAsyncFinallyBlock() + { + // Arrange + TestCase tc = new TestBuilder() + .AddDependencyInstance(false) + .AddTestBlock() + .AddAsyncFinallyBlock() + .Build(); + tc.ThrowOnFinallyBlockException = false; + + // Act + await Assert.ThrowsAsync(() => tc.ExecuteAsync()); + + // Assert + Assert.False(tc.Passed, "Test case did not get marked as Failed when we expected it."); + } } } diff --git a/IntelliTect.TestTools.TestFramework.Tests/TestData/Dependencies/SimulatorClasses.cs b/IntelliTect.TestTools.TestFramework.Tests/TestData/Dependencies/SimulatorClasses.cs index fb76a2f..a5ed5d9 100644 --- a/IntelliTect.TestTools.TestFramework.Tests/TestData/Dependencies/SimulatorClasses.cs +++ b/IntelliTect.TestTools.TestFramework.Tests/TestData/Dependencies/SimulatorClasses.cs @@ -110,6 +110,15 @@ public void Execute(bool result) } } + public class ExampleAsyncFinallyBlock : TestBlock + { + public async Task Execute(bool result) + { + await Task.Delay(1); + Assert.True(result, "This is an expected failure."); + } + } + public class ExampleAsyncBlockWithReturn : TestBlock { public async Task Execute() diff --git a/IntelliTect.TestTools.TestFramework/TestBuilder.cs b/IntelliTect.TestTools.TestFramework/TestBuilder.cs index efc1515..67c5e85 100644 --- a/IntelliTect.TestTools.TestFramework/TestBuilder.cs +++ b/IntelliTect.TestTools.TestFramework/TestBuilder.cs @@ -104,7 +104,7 @@ public TestBuilder AddAsyncFinallyBlock(params object[] testBlockArgs) where Block fb = CreateBlock(testBlockArgs); fb.IsFinallyBlock = true; fb.IsAsync = true; - TestBlocks.Add(fb); + FinallyBlocks.Add(fb); return this; }