Visual Studio Test Explorer doesn't finish running NUnit tests when exception in finalizer is thrown

143 Views Asked by At

My NUnit tests need to run some code from a third party dll. That code may throw exceptions during garbage collection. Specifically during Finalize methods. When such an exception occurs, the Visual Studio Test Explorer/Runner stops running the test suite, so I'm left with tests not run.

Since this only occurs if garbage collection is called before the test thread compelets, this is intermittent, but I can make it always reproducible for a test if I add

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

I cannot change the third party source code.

Below is a class that can show this behavior with an .Net 6 Nunit Test Project. You will notice how Test2 passes if ran alone, but never runs if running the whole fixture.

namespace TestProject
{
   public class Tests
   {
      [SetUp]
      public void Setup()
      {
      }

      [Test]
      public void Test1()
      {
         var x = new BrokenFinalizer();
         GC.Collect();
         GC.WaitForPendingFinalizers();
      }

      [Test]
      public void Test2()
      {
         var x = new BrokenFinalizer();
         GC.Collect();
         GC.WaitForPendingFinalizers();
      }
   }

   public class BrokenFinalizer
   {
      public BrokenFinalizer() { }

      ~BrokenFinalizer() 
      {
         throw new Exception();
      }
   }
}

How can I force Visual Studio Text Explorer/Runner to finish running all tests in the project, even when it needs to handle this "Broken Finalizer" case?

1

There are 1 best solutions below

0
Slack Groverglow On BEST ANSWER

Unfortunately, the best solution I could find is to use GC.SuppressFinalize for the third party objects that have this issue.

Since I may still want to run the Finalizer and abort at runtime I wrote this class to give the me some control of when the finalizer is suppressed

public class FinalizerControlled
{ 
   public FinalizerControlled(object value, bool forceSuppress = false)
   {
      if (IsTesting() || forceSuppress)
      {
         GC.SuppressFinalize(value);
      }
   }

   private static bool IsTesting()
   {
      ...
   }
}

Then for all instances of the EditOperation object that are causing unit tests abort I add the line

var _ = new FinalizerControlled(editOperation);