Am I doing something wrong combining dotMemory, xUnit and async
Asked Answered
C

2

6

I have a unit test where I try to verify that I have disposed of a document that was once attached to the main user interface. The unit test has to be async in that everything needs to be run under an STA thread and I have to await the user interface being created.

I have a helper that dispatches actions onto an STA thread.

I create the memory object in the main body of the test and then pass it to the async methods as below.

See the lines of code commented with ### to see the actual problem line. dotMemory is reporting that the object does not yet exist but I have already made an assertion proving that the object does exist.

( STA Helper classes can be found at https://gist.github.com/bradphelan/cb4f484fbf6a7f9829de0dd52036fd63 )

Is this a problem to do with async and dotMemory?

    [Collection("Memory leaks")]
public class MemLeakSpec
{
    public MemLeakSpec(ITestOutputHelper output)
    {
        DotMemoryUnitTestOutput.SetOutputMethod(output.WriteLine);
    }

    [Fact]
    [DotMemoryUnit(FailIfRunWithoutSupport = true)]
    public void ShouldCollectProjectX()
    {
        dotMemory.Check (memory => { STAThread.Run(() => ShouldCollectProjectAsyncX(memory)).Wait(); });
    }

    class Document { }

    class Container { public Document Document; };

    Document CreateFoo() => new Document();

    private async Task ShouldCollectProjectAsyncX(Memory memory)
    {
        await Task.Delay(TimeSpan.FromMilliseconds(50));

        Container container = new Container();

        memory.GetObjects(@where => @where.Type.Is<Document>())
                         .ObjectsCount.Should()
                         .Be(0);

        Document documentA = CreateFoo();
        container.Document = documentA;

        // Verify with dotMemory that the object exists.
        // ### This fails even though I have verified
        // ### the document exists
        memory.GetObjects(@where => @where.Type.Is<Document>())
                         .ObjectsCount.Should()
                         .Be(1);

        // Open a new project which should dispose the old one and thus
        // remove any reference to GenericWeinCadFolder
        container.Document = null;

        memory.GetObjects(@where => @where.Type.Is<Document>())
                         .ObjectsCount.Should()
                         .Be(0);


        GC.KeepAlive(container);
    }
}

I have created a synchronous version of the same above test and it doesn't fail. There are two tests below ShouldCollectAsync and ShouldCollectSync. The async one fails and the sync one passes.

[Collection("Memory leaks")]
public class MemLeakSpec
{
    public MemLeakSpec(ITestOutputHelper output)
    {
        DotMemoryUnitTestOutput.SetOutputMethod(output.WriteLine);
    }

    [Fact]
    [DotMemoryUnit(FailIfRunWithoutSupport = true)]
    public void ShouldCollectAsync()
    {
        dotMemory.Check (memory => { STAThread.Run(() => ShouldCollectProjectAsyncX(memory)).Wait(); });
    }

    /// This test is almost identical to the ShouldCollectAsync
    /// but it passes
    [Fact]
    [DotMemoryUnit(FailIfRunWithoutSupport = true)]
    public void ShouldCollectSync ()
    {
        dotMemory.Check (memory => { STAThread.Run(() => ShouldCollectProjectSync(memory)); });
    }

    class Document { }

    class Container { public Document Document; };

    Document CreateFoo() => new Document();

    private async Task ShouldCollectProjectSync(Memory memory)
    {
        Container container = new Container();

        memory.GetObjects(@where => @where.Type.Is<Document>())
                         .ObjectsCount.Should()
                         .Be(0);

        Document documentA = CreateFoo();
        container.Document = documentA;

        // Verify with dotMemory that the object exists.
        // #### Passes here
        memory.GetObjects(@where => @where.Type.Is<Document>())
                         .ObjectsCount.Should()
                         .Be(1);

        GC.KeepAlive(documentA);
        GC.KeepAlive(container);
    }
    private async Task ShouldCollectProjectAsyncX(Memory memory)
    {

        await Task.Delay(TimeSpan.FromMilliseconds(50));

        Container container = new Container();

        memory.GetObjects(@where => @where.Type.Is<Document>())
                         .ObjectsCount.Should()
                         .Be(0);

        Document documentA = CreateFoo();
        container.Document = documentA;

        // Verify with dotMemory that the object exists.
        // #### FAILS here
        memory.GetObjects(@where => @where.Type.Is<Document>())
                         .ObjectsCount.Should()
                         .Be(1);

        GC.KeepAlive(documentA);
        GC.KeepAlive(container);
    }
}
Concha answered 5/12, 2016 at 6:52 Comment(8)
I've tried putting the dotMemory.Check call inside the async method but dotMemory tells me I am trying to run parallel tests.Concha
Does get objects run asynch?Raye
No it does not.Concha
Maybe I wrong but when you declare the method asynch it makes the operations inside the method run asynchronously. Thus when you assign to the document it isn't finished by the time right after on the next operation where you are checking it isn't zero. The key word 'await' will force it to wait till finished. So like this 'await document adocument ...'Raye
You are wrong. async does not make all lines within the method run asynchronously. I suggest you actually write some async code before commenting :). If CreateFoo has been declared as Task<Document> CreateFoo()rather than Document CreateFoo() you would have had a point but then the code would not have compiled.Concha
Document ADocument = await foo(). See msdn keyword await. Maybe I interpret it wrong but asynch word makes everything run asynchronously which is why you use one or more awaits for dependent tasks. Good for things where most tasks are independent.Raye
I haven't written much asynch c# but I have c++ multithreaded so I'm sorry if wrong but I think that is how it works. Msdn says by "tasks" to be precise so maybe not lines per say.Raye
@marshalcraft I appreciate you trying to help but you don't understand async/await in C#. Start here msdn.microsoft.com/library/hh191443(vs.110).aspx ( i have been programming async/await code daily in C# for 4 years )Concha
F
2

dotMemory Unit requires that all its method should be called from the "test" method. Think about that like there is a call dotMemoryUnit.TestStart at the very begin of ShouldCollectAsync and dotMemoryUnit.TestEnd on finish. You did not expose implementation of STAThread.Run, so I can't give more detailed advise, but the idea is to wait in the test method while you async routine is finished.

Faria answered 5/12, 2016 at 9:27 Comment(1)
I'd just come to the same conclusion myself.Concha
O
2

For me was enough to wrap test in a local function:

public void TestMethod()
{
    async Task LocalFunction()
    {
        // here you can make async calls
        await Task.Delay(...);
    }

    LocalFunction().ConfigureAwait(true).GetAwaiter().GetResult();
}
Olivarez answered 9/6, 2020 at 10:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.