Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NUnit does not fail on exception in Finalizer

In our framework, there is some key objects which have file handles or WCF client connections. Those objects are IDiposable and we have validation code (with exceptions being thrown) to ensure that they are getting properly disposed when not needed anymore. (Debug-only so that we don't want to crash on release). This is not necessarily on shutdown.

On top of this, we have unit tests which run our code and we thus expect them to fail if we forget such disposals.

Problem is: On .NET 4.5.1, with NUnit (2.6.3.13283) runner (or with ReSharper, or TeamCity) does not trigger test failure when such exception in the Finalizer are thrown.

Weird thing is: Using NCrunch (with is over NUnit also), unit tests DO fail! (Which locally for me, at least I can find such missing disposals)

That's pretty bad, since our build machine (TeamCity) does not see such failures and we think that everything is good! But running our software (in debug) will indeed crash, showing that we forgot a disposal

Here's an example that shows that NUnit does not fail

public class ExceptionInFinalizerObject
{
    ~ExceptionInFinalizerObject()
    {
        //Tried here both "Assert.Fail" and throwing an exception to be sure
        Assert.Fail();
        throw new Exception();
    }
}

[TestFixture]
public class FinalizerTestFixture
{
    [Test]
    public void FinalizerTest()
    {
        CreateFinalizerObject();

        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    public void CreateFinalizerObject()
    {
        //Create the object in another function to put it out of scope and make it available for garbage collection
        new ExceptionInFinalizerObject();
    }
}

Running this in the NUnit runner: everything is green. Asking ReSharper to debug this test will indeed step into the Finalizer.

like image 662
FrankyB Avatar asked Mar 25 '26 08:03

FrankyB


1 Answers

So with the help of Eric Lippert, I found out that Exceptions are not caught by NUnit when they are on another thread. So the same happens to the finalizer thread.

I tried finding a solution in the settings of NUnit, but to no avail.

So I came up with subclassing all my TestFixture, so that there is a common [SetUp] and [TearDown] to all my tests:

public class BaseTestFixture
{
    private UnhandledExceptionEventHandler _unhandledExceptionHandler;
    private bool _exceptionWasThrown;

    [SetUp]
    public void UnhandledExceptionRegistering()
    {
        _exceptionWasThrown = false;
        _unhandledExceptionHandler = (s, e) =>
        {
            _exceptionWasThrown = true;
        };

        AppDomain.CurrentDomain.UnhandledException += _unhandledExceptionHandler;
    }

    [TearDown]
    public void VerifyUnhandledExceptionOnFinalizers()
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();

        Assert.IsFalse(_exceptionWasThrown);

        AppDomain.CurrentDomain.UnhandledException -= _unhandledExceptionHandler;
    }
}

Obviously, with this code I can only know that an exception was thrown, but I don't know which one. However, for my usage, it is sufficient. If I change it later, I'll try and update this one (or if someone has a better solution, I'm glad to set as a solution!)

I had two scenarios I needed to cover, so I include them here:

[TestFixture]
public class ThreadExceptionTestFixture : BaseTestFixture
{
    [Test, Ignore("Testing-Testing test: Enable this test to validate that exception in threads are properly caught")]
    public void ThreadExceptionTest()
    {
        var crashingThread = new Thread(CrashInAThread);
        crashingThread.Start();
        crashingThread.Join(500);
    }

    private static void CrashInAThread()
    {
        throw new Exception();
    }

    [Test, Ignore("Testing-Testing test: Enable this test to validate that exceptions in Finalizers are properly caught")]
    public void FinalizerTest()
    {
        CreateFinalizerObject();

        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    public void CreateFinalizerObject()
    {
        //Create the object in another function to put it out of scope and make it available for garbage collection
        new ExceptionInFinalizerObject();
    }
}

public class ExceptionInFinalizerObject
{
    ~ExceptionInFinalizerObject()
    {
        throw new Exception();
    }
}

As for why NCrunch does it properly, that's a good question...

like image 107
FrankyB Avatar answered Mar 26 '26 22:03

FrankyB



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!