5

The question is how can I test the fact that object disposes resources when finalise is called. The code for the class:

public class TestClass : IDisposable {

    public bool HasBeenDisposed {get; private set; }

    public void Dispose() {
        HasBeenDisposed = true;
    }

    ~TestClass() {
        Dispose();
    }
}

Please note that I don't care about the correct implementation of Dispose/Finalize just now as I want to find the way to test it first. At this stage it is enough to assume the HasBeenDisposed will be set to true if Dispose/Finalize ware called.

The actual test I wrote looks like:
UPDATED WITH WEAKREFERENCE:

[Test]
public void IsCleanedUpOnGarbadgeCollection() {
    var o = new TestClass();
    o.HasBeenDisposed.Should().Be.False();

    **var weak = new WeakReference(o, true); // true =Track after finalisation
    o = null; // Make eligible for GC**

    GC.Collect(0, GCCollectionMode.Forced);
    GC.WaitForPendingFinalizers();


    **((TestClass)weak.Target)**.HasBeenDisposed.Should().Be.True();
}

or the code I like better (ADDED AFTER UPDATE):

[Test]
public void IsCleanedUpOnGarbadgeCollection() {
    WeakReference weak = null;

    // Use action to isolate instance and make them eligible for GC
    // Use WeakReference to track the object after finalisaiton
    Action act = () = {
        var o = new TestClass();
        o.HasBeenDisposed.Should().Be.False();
        weak = new WeakReference(o, true); // True=Track reference AFTER Finalize
    };

    act();

    GC.Collect(0, GCCollectionMode.Forced);
    GC.WaitForPendingFinalizers();

    // No access to o variable here which forces us to use WeakReference only to avoid error
    ((TestClass)weak.Target).HasBeenDisposed.Should().Be.True();
}

This test fails (PASSES AFTER UPDATE) but I observe following (UPDATED):

  1. GC.WaitForPendingFinalizers() does suspend the thread and finalises instance in o, but only if is not rooted. Assigned NULL to it and used WeakReference to get it AFTER finalisation.
  2. Finilize (destructor) code is executed at correct point when the o does not hold the instance.

So what is the correct way of testing this. What do I miss?

I suppose it is the variable o that prevents GC from collection it.
UPDATE: Yes it is the issue. Had to use WeakReference instead.

Dmytrii Nagirniak
  • 23,696
  • 13
  • 75
  • 130
  • This was exactly what I needed. Your use of an Action delegate got my code into a separate stack frame and allowed my unit test to properly collect the objects. – NathanAW Jul 02 '10 at 04:30
  • Thanks for this solution, it works great. For me it works without the delegate by simply setting the original variable to null. (edit: Sorry for necropost, didn't notice date) – amnesia Feb 23 '17 at 21:20

3 Answers3

3

"I suppose it is the variable o that prevents GC from collection it." Correct. The existence of a reference on the stack means the object is reachable, and therefore not eligible for collection (and finalisation).

Because an object will not be finalised until there are no references to it, testing finalisation behaviour is likely to be tricky. (You need a reference to the object in order to make assertions about it!) One way is to do it indirectly: have the object send some sort of message during finalisation. But this distorts the finalisation code purely for test purposes. You could also hold a weak reference to the object, which would make it eligible for finalisation, and have it resurrect itself in the finaliser -- but again you don't want to have it resurrect itself in production code.

itowlson
  • 73,686
  • 17
  • 161
  • 157
  • I changed code to use WeakReference with tracking Target after finalisation. Updated the question with the full code. Thanks a lot for giving me the idea. All things are simple if you think simple :) – Dmytrii Nagirniak Oct 21 '09 at 01:52
0

Why would the object be collected if there is a local reference to it?

dgstl
  • 61
  • 5
0

A memory profiler is the most appropriate way to test for leaks.

I could recommend the .Net Memory Profiler.

Doguhan Uluca
  • 6,933
  • 4
  • 38
  • 51