我在使用 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
我知道问题出在哪里了。它是如此简单,以至于令人惊讶的是我花了多少时间与这个看似可怕的错误作斗争。要在 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.