Search code examples
c#nunitspecflow

When and by "whom" is the Dispose() method called after scenario


I use SpecFlow.NUnit. Let's say I have 2 scenarios and I run them

Scenario: First
Given Definition1

Scenario: Second
Given Definition2

[Given (@"Definition1")]
public void Definition1{
  File.Open(@"C:\Users\1.txt", FileMode.Open);
}

[Given (@"Definition2")]
public void Definition2{
  File.Open(@"C:\Users\1.txt", FileMode.Open); // no error thrown
}

I am trying to undertand how is that that after the "First" scenario runs and opens a FileStream and method Definition1() does NOT close it, no error is thrown when the "Second" scenario opens a FileStream on the same file. This leads me into thinking that sdomewhere the Dispose(); is called, but where and by whom? maybe this is not a specflow thing but NUnit thing?


Solution

  • I don't have a definitive answer but I have some informed speculation.

    You are correct that something is closing the file between the first and second opens; the default sharing mode for File.Open is "no sharing even in the same process", and you should therefore get the (confusing and wrong) error message "The process cannot access the file because it is being used by another process." (The error message should say the truth: "The file cannot be opened because it is already opened for exclusive access".)

    What then is closing the file? First, understand how finalization works in C#.

    • The correct thing to do is to call Dispose or otherwise close the file. A correct implementation of Dispose will close the file and mark the object as not requiring finalization. The finalizer thread then never sees the object.

    • The incorrect thing to do is to skip closing/disposing. Eventually the garbage collector will run and notice that an object which requires finalization is dead. That gets put onto a queue, and the finalizer thread eventually executes the finalizer, closing the file.

    Since you are not disposing of the file, something must be closing it. I can think of four possibilities; there may be others.

    (1) You are getting lucky (or unlucky, depending on your point of view) and the garbage collector is happening to run, finding the orphaned file, putting it on the finalizer queue, and the finalizer runs before the second attempt to open the file.

    (2) Your test framework is calling GC.Collect and GC.WaitForPendingFinalizers between tests, forcing the resources to be cleaned up.

    (3) Your test framework is putting each test into an AppDomain and unloading the AppDomain between tests. That should also force resources to be cleaned up.

    (4) Your test framework is running each test in a separate process. Exclusive locks on files do not survive process termination.

    Without knowing the details of how NUnit works, I couldn't tell you which of the above scenarios you're in, but you're likely in one of them.