Подтвердить что ты не робот

Как написать unit test, чтобы определить, может ли объект быть собранным мусором?

В отношении моего предыдущего вопроса, мне нужно проверить, будет ли компонент, который будет создан экземпляром Castle Windsor, может быть собран из мусора после того, как мой код завершил его использование, Я попробовал предложение в ответах на предыдущий вопрос, но, похоже, он работает не так, как ожидалось, по крайней мере для моего кода. Поэтому я хотел бы написать unit test, который проверяет, может ли конкретный экземпляр объекта быть мусором, собранным после запуска моего кода.

Можно ли сделать это надежным способом?

ИЗМЕНИТЬ

В настоящее время у меня есть следующий тест, основанный на ответе Павла Стовелла, который преуспевает:

     [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--;
        }
    }

Правильно ли я полагаю, что, основываясь на вышеприведенном тесте, я могу заключить, что Windsor не будет утечки памяти при использовании NoTrackingReleasePolicy?

4b9b3361

Ответ 1

Это то, что я обычно делаю:

[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: Очень много раз, когда вы должны вызывать GC.Collect() в производственном приложении. Но тестирование на утечки - один из примеров того, где это уместно.

Ответ 2

Возможно, вы могли бы провести WeakReference, а затем проверить, чтобы он больше не был жив (т.е., IsAlive) после тесты завершены.

Ответ 3

Основываясь на на ответе Павла, я создал более многоразовый метод Assert. Поскольку string копируются по значению, я добавил для них явную проверку. Они могут собираться сборщиком мусора.

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 );
}

Следующие модульные тесты показывают, что функция работает в некоторых распространенных сценариях.

[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 );
}

Из-за различий при использовании конфигурации Release (я предполагаю, что оптимизация компилятора) некоторые из этих модульных тестов потерпят неудачу, если не было вызвано GC.KeepAlive().

Полный исходный код (включая некоторые из используемых вспомогательных методов) можно найти в моей библиотеке.

Ответ 4

Используйте dotMemory Unit (бесплатно)

[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");
}

Ответ 5

Это не ответ, однако вы можете попробовать запустить свой код в режимах Отладка и Release (для сравнения).

По моему опыту Отладка версия JIT-кода упрощена для отладки и, следовательно, может видеть, что ссылки остаются живыми дольше (я верю функции). Однако код JITed в Release режим может иметь объекты, готовые к сбору, быстро, как только он выходит из сферы действия, и если происходит сборка.

Также не отвечая на ваш вопрос::-)
Мне было бы интересно увидеть, как вы отлаживаете этот код с помощью Visual Studio в режиме Interop (Managed and Native), а затем разбиваетесь после отображения окна сообщения или чего-то еще. Затем вы можете открыть Debug- > Windows-Immediate, а затем напечатать

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

(или вы можете использовать Windbg, как другие, опубликованные в предыдущих сообщениях)

Спасибо, Аарон