XUnit/NUnit 测试中 Avalonia WriteableBitmap 内存损坏,但运行的应用程序中没有损坏

问题描述 投票:0回答:1

我在使用 XUnit 运行测试时遇到内存损坏问题(并且使用 NUnit 遇到相同的行为),即使使用看似简单的代码也是如此。不涉及多线程或复杂的依赖关系。根据 Avalonia 文档,测试项目是为无头测试而设置的。

这是简化的测试用例:

[AvaloniaFact]
public unsafe void TestWriteableBitmap()
{
    var image = new WriteableBitmap(new PixelSize(1024, 1024), new Vector(96, 96), PixelFormats.Rgba8888);
    
    using (var lockedBitmap = image.Lock())
    {
        //make all 0
        Unsafe.InitBlock(lockedBitmap.Address.ToPointer(), 0, (uint)(lockedBitmap.RowBytes * lockedBitmap.Size.Height));

        //write 0
        using (var stream = File.OpenWrite($"/home/qserj1/test/000.bit"))
        {
            var pixels = new byte[(uint)(lockedBitmap.RowBytes * lockedBitmap.Size.Height)];
            Marshal.Copy(lockedBitmap.Address, pixels, 0, pixels.Length);
            stream.Write(pixels, 0, pixels.Length);
        }
        
    }

    //write 1
    using (var lockedBitmap = image.Lock())
    {
        using (var stream = File.OpenWrite($"/home/qserj1/test/001.bit"))
        {
            var pixels = new byte[(uint)(lockedBitmap.RowBytes * lockedBitmap.Size.Height)];
            Marshal.Copy(lockedBitmap.Address, pixels, 0, pixels.Length);
            stream.Write(pixels, 0, pixels.Length);
        }
    }
}

测试创建一个 WriteableBitmap,将其内存初始化为零,并将数据写入“000.bit”。然后,它再次锁定位图并将数据写入“001.bit”。我希望这两个文件仅包含零,但第二个文件(“001.bit”)包含垃圾数据。

在调试模式与发布模式下运行测试似乎存在一些相关性,但尚不清楚。最令人困惑的部分是,当此代码集成到正在运行的 Avalonia 应用程序中时,文件始终包含零,正如预期的那样。我需要优化一个使用 WriteableBitmap 从网络数据创建图像的过程,并且我在测试环境中的图像中遇到了意外的垃圾数据。

如何解释测试环境和运行应用程序之间的这种差异?使用 XUnit/NUnit 在 Avalonia 中编写涉及 WriteableBitmap 的测试有什么具体注意事项吗?

阿瓦隆尼亚 11.2(也是 11.1.4) xUnit 2.6.6

测试应用程序:https://drive.google.com/file/d/1b18DcQMMcMSzQq8c2PB5XPVW52Zsh7D-/view?usp=sharing

c# xunit avalonia writeablebitmap memory-corruption
1个回答
0
投票

我知道问题出在哪里了。它是如此简单,以至于令人惊讶的是我花了多少时间与这个看似可怕的错误作斗争。要在 XUnit 测试环境中使用 Avalonia 对象,请使用 Avalonia.Headless.XUnit 包。它提供了 Avalonia 环境的创建以及创建其对象并使用它们执行各种操作的能力。

因此,当使用 Headless.XUnit 时在 WriteableBitmap 上调用 .Lock() 时,我们最终得到以下函数(位于 HeadlessPlatformRenderInterface.cs 文件中):

public ILockedFramebuffer Lock()
{
    Version++;
    var mem = Marshal.AllocHGlobal(PixelSize.Width * PixelSize.Height * 4);
    return new LockedFramebuffer(mem, PixelSize, PixelSize.Width * 4, Dpi, PixelFormat.Rgba8888,
        () => Marshal.FreeHGlobal(mem));
}```

Do you understand what this does? Every time Lock() is called, it allocates a new buffer in memory and returns a new LockedFramebuffer object created over this memory block. At the end of its lifecycle, it frees this buffer. Each time, it’s a new buffer—uninitialized. Simply put, this behavior is completely incorrect and bears no resemblance to the actual functioning of WriteableBitmap, aside from returning a buffer of the size expected by the image. This is the whole problem that nearly drove me crazy :-)

I still have a question: is it possible to use something other than the Headless stub in the XUnit environment? It seems like a slightly different question. After all, you could write a test application without any testing frameworks, and it would be quite reliable.
© www.soinside.com 2019 - 2024. All rights reserved.