From fab999316ee73e56c046f3bd34928e1b0e8d65ab Mon Sep 17 00:00:00 2001 From: ParantezTech <12572227+par274@users.noreply.github.com> Date: Sun, 11 Oct 2026 06:11:56 +0300 Subject: [PATCH] [Tests] Cover concurrent resolve answers and release device slabs --- .../Gpu/Vulkan/HeadlessVulkan.cs | 1 + .../VideoOut/ConcurrentResolveTests.cs | 75 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 tests/SharpEmu.Libs.Tests/VideoOut/ConcurrentResolveTests.cs diff --git a/tests/SharpEmu.Libs.Tests/Gpu/Vulkan/HeadlessVulkan.cs b/tests/SharpEmu.Libs.Tests/Gpu/Vulkan/HeadlessVulkan.cs index 5c3f36f..60068f5 100644 --- a/tests/SharpEmu.Libs.Tests/Gpu/Vulkan/HeadlessVulkan.cs +++ b/tests/SharpEmu.Libs.Tests/Gpu/Vulkan/HeadlessVulkan.cs @@ -161,6 +161,7 @@ internal sealed unsafe class HeadlessVulkan : IDisposable public void Dispose() { Vk.DeviceWaitIdle(Device); + _deviceInfo?.Slabs.Destroy(); Vk.DestroyDevice(Device, null); if (_debugUtils is { } debugUtils) { diff --git a/tests/SharpEmu.Libs.Tests/VideoOut/ConcurrentResolveTests.cs b/tests/SharpEmu.Libs.Tests/VideoOut/ConcurrentResolveTests.cs new file mode 100644 index 0000000..84652a3 --- /dev/null +++ b/tests/SharpEmu.Libs.Tests/VideoOut/ConcurrentResolveTests.cs @@ -0,0 +1,75 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using SharpEmu.Libs.VideoOut; +using Xunit; +using static SharpEmu.Libs.VideoOut.VulkanVideoPresenter; + +namespace SharpEmu.Libs.Tests.VideoOut; + +public sealed class ConcurrentResolveTests +{ + [Fact] + public void ConsecutiveAndRepeatedReadsKeepOneProofPerQuery() + { + var assumptions = new ConcurrentAssumptions(1, 2); + assumptions.Record(ConcurrentQuery.ResidentClean, 0x1000, 4, false); + assumptions.Record(ConcurrentQuery.CleanWord, 0x1000, 4, false); + var allocated = GC.GetAllocatedBytesForCurrentThread(); + for (var index = 0; index < 100000; index++) + { + var address = 0x1000UL + (ulong)index * 4; + assumptions.Record(ConcurrentQuery.ResidentClean, address, 4, false); + assumptions.Record(ConcurrentQuery.CleanWord, address, 4, false); + assumptions.Record(ConcurrentQuery.ResidentClean, address, 4, false); + } + + Assert.Equal(allocated, GC.GetAllocatedBytesForCurrentThread()); + Assert.Equal(2, assumptions.Answers.Count); + Assert.All(assumptions.Answers, proof => + { + Assert.Equal(0x1000UL, proof.Address); + Assert.Equal(400000UL, proof.Size); + Assert.False(proof.Answer); + }); + Assert.True(assumptions.ReadsImageOwnership); + } + + [Fact] + public void OverlappingReadsMergeWithoutAddingUnreadGaps() + { + var assumptions = new ConcurrentAssumptions(1, 2); + assumptions.Record(ConcurrentQuery.DirtyBytes, 0x1020, 0x20, false); + assumptions.Record(ConcurrentQuery.DirtyBytes, 0x1000, 0x30, false); + assumptions.Record(ConcurrentQuery.DirtyBytes, 0x1050, 0x10, false); + + Assert.Equal((ConcurrentQuery.DirtyBytes, 0x1000UL, 0x40UL, false), assumptions.Answers[0]); + Assert.Equal((ConcurrentQuery.DirtyBytes, 0x1050UL, 0x10UL, false), assumptions.Answers[1]); + Assert.False(assumptions.ReadsImageOwnership); + } + + [Fact] + public void OwnedReadsAndDifferentOwnershipRulesRemainSeparate() + { + var assumptions = new ConcurrentAssumptions(1, 2); + assumptions.Record(ConcurrentQuery.Resident, 0x1000, 4, false); + assumptions.Record(ConcurrentQuery.ResidentClean, 0x1000, 4, false); + assumptions.Record(ConcurrentQuery.Resident, 0x1004, 4, true); + assumptions.Record(ConcurrentQuery.Resident, 0x1008, 4, true); + assumptions.Record(ConcurrentQuery.Resident, 0x1004, 4, true); + + Assert.Equal(4, assumptions.Answers.Count); + Assert.Equal(2, assumptions.Answers.Count(proof => proof.Answer)); + Assert.True(assumptions.ReadsImageOwnership); + } + + [Fact] + public void WrappingRangesDoNotMergeWithLowAddresses() + { + var assumptions = new ConcurrentAssumptions(1, 2); + assumptions.Record(ConcurrentQuery.Resident, ulong.MaxValue - 1, 4, false); + assumptions.Record(ConcurrentQuery.Resident, 0, 4, false); + + Assert.Equal(2, assumptions.Answers.Count); + } +}