How can I write a unit test to determine whether a

2019-01-08 06:30发布

In relation to my previous question, I need to check whether a component that will be instantiated by Castle Windsor, can be garbage collected after my code has finished using it. I have tried the suggestion in the answers from the previous question, but it does not seem to work as expected, at least for my code. So I would like to write a unit test that tests whether a specific object instance can be garbage collected after some of my code has run.

Is that possible to do in a reliable way ?

EDIT

I currently have the following test based on Paul Stovell's answer, which succeeds:

     [TestMethod]
    public void ReleaseTest()
    {
        WindsorContainer container = new WindsorContainer();
        container.Kernel.ReleasePolicy = new NoTrackingReleasePolicy();
        container.AddComponentWithLifestyle<ReleaseTester>(LifestyleType.Transient);
        Assert.AreEqual(0, ReleaseTester.refCount);
        var weakRef = new WeakReference(container.Resolve<ReleaseTester>());
        Assert.AreEqual(1, ReleaseTester.refCount);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Assert.AreEqual(0, ReleaseTester.refCount, "Component not released");
    }

    private class ReleaseTester
    {
        public static int refCount = 0;

        public ReleaseTester()
        {
            refCount++;
        }

        ~ReleaseTester()
        {
            refCount--;
        }
    }

Am I right assuming that, based on the test above, I can conclude that Windsor will not leak memory when using the NoTrackingReleasePolicy ?

5条回答
我想做一个坏孩纸
2楼-- · 2019-01-08 07:10

Perhaps you could hold a WeakReference to it and then check to see that it no longer alive (i.e., !IsAlive) after the tests have completed.

查看更多
放我归山
3楼-- · 2019-01-08 07:16

Based on Paul's answer, I created a more reusable Assert method. Since string's are copied by value I added an explicit check for them. They can be collected by the garbage collector.

public static void IsGarbageCollected<TObject>( ref TObject @object )
    where TObject : class
{
    Action<TObject> emptyAction = o => { };
    IsGarbageCollected( ref @object, emptyAction );
}

public static void IsGarbageCollected<TObject>(
    ref TObject @object,
    Action<TObject> useObject )
    where TObject : class
{
    if ( typeof( TObject ) == typeof( string ) )
    {
        // Strings are copied by value, and don't leak anyhow.
        return;
    }

    int generation = GC.GetGeneration( @object );
    useObject( @object );
    WeakReference reference = new WeakReference( @object, true );
    @object = null;

    // The object should have gone out of scope about now, 
    // so the garbage collector can clean it up.
    GC.Collect( generation, GCCollectionMode.Forced );
    GC.WaitForPendingFinalizers();

    Assert.IsNull( reference.Target );
}

The following unit tests show the function is working in some common scenarios.

[TestMethod]
public void IsGarbageCollectedTest()
{
    // Empty object without any references which are held.
    object empty = new object();
    AssertHelper.IsGarbageCollected( ref empty );

    // Strings are copied by value, but are collectable!
    string @string = "";
    AssertHelper.IsGarbageCollected( ref @string );

    // Keep reference around.
    object hookedEvent = new object();
    #pragma warning disable 168
    object referenceCopy = hookedEvent;
    #pragma warning restore 168
    AssertHelper.ThrowsException<AssertFailedException>(
        () => AssertHelper.IsGarbageCollected( ref hookedEvent ) );
    GC.KeepAlive( referenceCopy );

    // Still attached as event.
    Publisher publisher = new Publisher();
    Subscriber subscriber = new Subscriber( publisher );
    AssertHelper.ThrowsException<AssertFailedException>(
        () => AssertHelper.IsGarbageCollected( ref subscriber ) );
    GC.KeepAlive( publisher );
}

Due to differences when using the Release configuration (I assume compiler optimizations), some of these unit tests would fail if GC.KeepAlive() were not to be called.

Complete source code (including some of the helper methods used) can be found in my library.

查看更多
走好不送
4楼-- · 2019-01-08 07:21

This is not an answer, however you may want to try running your code in both Debug and Release modes (for comparison sake).

In my experience the Debug version of JIT'ed code is made easier to debug and thus may see references stay alive longer (I belive function scope) However, code JITed in Release mode may have the objects ready for collection quickly once it is out of scope and if a Collection happens.

Also not answering your question: :-)
I would be interested in seeing you debug this code using Visual Studio in Interop mode (Managed and Native) and then breaking after displaying a message box or something. Then you can open the Debug->Windows-Immediate and then type

load sos
(Change to thread 0)
!dso
!do <object>
!gcroot <object> (and look for any roots)

(or you can use Windbg as other's have posted in previous posts)

Thanks, Aaron

查看更多
爷、活的狠高调
5楼-- · 2019-01-08 07:22

This is what I normally do:

[Test]
public void MyTest() 
{
    WeakReference reference;
    new Action(() => 
    {
        var service = new Service();
        // Do things with service that might cause a memory leak...

        reference = new WeakReference(service, true);
    })();

    // Service should have gone out of scope about now, 
    // so the garbage collector can clean it up
    GC.Collect();
    GC.WaitForPendingFinalizers();

    Assert.IsNull(reference.Target);
}

NB: There are very, very few times where you should call GC.Collect() in a production application. But testing for leaks is one example of where it's appropriate.

查看更多
劫难
6楼-- · 2019-01-08 07:27

Use dotMemory Unit framework (it's free)

[TestMethod]
public void ReleaseTest()
{
    // arrange
    WindsorContainer container = new WindsorContainer();
    container.Kernel.ReleasePolicy = new NoTrackingReleasePolicy();
    container.AddComponentWithLifestyle<ReleaseTester>(LifestyleType.Transient);
    var target = container.Resolve<ReleaseTester>()

    // act
    target = null;

    // assert        
    dotMemory.Check(memory =>
      Assert.AreEqual(
        0, 
        memory.GetObjects(where => where.Type.Is<ReleaseTester>().ObjectsCount, 
        "Component not released");
}
查看更多
登录 后发表回答