Convert Ryujinx.Tests.Memory tests to xUnit
This commit is contained in:
parent
7b653bbf8d
commit
f0441bf28b
5 changed files with 157 additions and 122 deletions
|
@ -79,7 +79,7 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
IEnumerable<MemoryRange> IVirtualMemoryManager.GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
return NoMappings ? Array.Empty<MemoryRange>() : new MemoryRange[] { new MemoryRange(va, size) };
|
||||
return NoMappings ? Array.Empty<MemoryRange>() : new[] { new MemoryRange(va, size) };
|
||||
}
|
||||
|
||||
public bool IsMapped(ulong va)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
using NUnit.Framework;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Ryujinx.Tests.Memory
|
||||
{
|
||||
public class MultiRegionTrackingTests
|
||||
public class MultiRegionTrackingTests : IDisposable
|
||||
{
|
||||
private const ulong MemorySize = 0x8000;
|
||||
private const int PageSize = 4096;
|
||||
|
@ -16,17 +16,16 @@ namespace Ryujinx.Tests.Memory
|
|||
private MemoryTracking _tracking;
|
||||
private MockVirtualMemoryManager _memoryManager;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
public MultiRegionTrackingTests()
|
||||
{
|
||||
_memoryBlock = new MemoryBlock(MemorySize);
|
||||
_memoryManager = new MockVirtualMemoryManager(MemorySize, PageSize);
|
||||
_tracking = new MemoryTracking(_memoryManager, PageSize);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
_memoryBlock.Dispose();
|
||||
}
|
||||
|
||||
|
@ -56,8 +55,8 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
handle.QueryModified(startAddress, size, (address, range) =>
|
||||
{
|
||||
Assert.IsTrue(addressPredicate(address)); // Written pages must be even.
|
||||
Assert.GreaterOrEqual(address, lastAddress); // Must be signalled in ascending order, regardless of write order.
|
||||
Assert.True(addressPredicate(address)); // Written pages must be even.
|
||||
Assert.True(address >= lastAddress); // Must be signalled in ascending order, regardless of write order.
|
||||
lastAddress = address;
|
||||
regionCount++;
|
||||
});
|
||||
|
@ -72,8 +71,8 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
handle.QueryModified(startAddress, size, (address, range) =>
|
||||
{
|
||||
Assert.IsTrue(addressPredicate(address)); // Written pages must be even.
|
||||
Assert.GreaterOrEqual(address, lastAddress); // Must be signalled in ascending order, regardless of write order.
|
||||
Assert.True(addressPredicate(address)); // Written pages must be even.
|
||||
Assert.True(address >= lastAddress); // Must be signalled in ascending order, regardless of write order.
|
||||
lastAddress = address;
|
||||
regionCount++;
|
||||
}, sequenceNumber);
|
||||
|
@ -93,12 +92,14 @@ namespace Ryujinx.Tests.Memory
|
|||
{
|
||||
resultAddress = address;
|
||||
});
|
||||
Assert.AreEqual(resultAddress, (ulong)i * PageSize + address);
|
||||
Assert.Equal((ulong)i * PageSize + address, resultAddress);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DirtyRegionOrdering([Values] bool smart)
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void DirtyRegionOrdering(bool smart)
|
||||
{
|
||||
const int PageCount = 32;
|
||||
IMultiRegionHandle handle = GetGranular(smart, 0, PageSize * PageCount, PageSize);
|
||||
|
@ -119,7 +120,7 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
int oddRegionCount = ExpectQueryInOrder(handle, 0, PageSize * PageCount, (address) => (address / PageSize) % 2 == 1);
|
||||
|
||||
Assert.AreEqual(oddRegionCount, PageCount / 2); // Must have written to all odd pages.
|
||||
Assert.Equal(PageCount / 2, oddRegionCount); // Must have written to all odd pages.
|
||||
|
||||
// Write to all the even pages.
|
||||
RandomOrder(random, even, (i) =>
|
||||
|
@ -129,11 +130,13 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
int evenRegionCount = ExpectQueryInOrder(handle, 0, PageSize * PageCount, (address) => (address / PageSize) % 2 == 0);
|
||||
|
||||
Assert.AreEqual(evenRegionCount, PageCount / 2);
|
||||
Assert.Equal(PageCount / 2, evenRegionCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SequenceNumber([Values] bool smart)
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void SequenceNumber(bool smart)
|
||||
{
|
||||
// The sequence number can be used to ignore dirty flags, and defer their consumption until later.
|
||||
// If a user consumes a dirty flag with sequence number 1, then there is a write to the protected region,
|
||||
|
@ -172,7 +175,7 @@ namespace Ryujinx.Tests.Memory
|
|||
}, 1);
|
||||
}
|
||||
|
||||
Assert.AreEqual(oddRegionCount, PageCount / 2); // Must have written to all odd pages.
|
||||
Assert.Equal(PageCount / 2, oddRegionCount); // Must have written to all odd pages.
|
||||
|
||||
// Write to all pages.
|
||||
|
||||
|
@ -182,22 +185,22 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
int evenRegionCount = ExpectQueryInOrder(handle, 0, PageSize * PageCount, (address) => (address / PageSize) % 2 == 0, 1);
|
||||
|
||||
Assert.AreEqual(evenRegionCount, PageCount / 2); // Must have written to all even pages.
|
||||
Assert.Equal(PageCount / 2, evenRegionCount); // Must have written to all even pages.
|
||||
|
||||
oddRegionCount = 0;
|
||||
|
||||
handle.QueryModified(0, PageSize * PageCount, (address, range) => { oddRegionCount++; }, 1);
|
||||
|
||||
Assert.AreEqual(oddRegionCount, 0); // Sequence number has not changed, so found no dirty subregions.
|
||||
Assert.Equal(0, oddRegionCount); // Sequence number has not changed, so found no dirty subregions.
|
||||
|
||||
// With sequence number 2, all all pages should be reported as modified.
|
||||
|
||||
oddRegionCount = ExpectQueryInOrder(handle, 0, PageSize * PageCount, (address) => (address / PageSize) % 2 == 1, 2);
|
||||
|
||||
Assert.AreEqual(oddRegionCount, PageCount / 2); // Must have written to all odd pages.
|
||||
Assert.Equal(PageCount / 2, oddRegionCount); // Must have written to all odd pages.
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Fact]
|
||||
public void SmartRegionTracking()
|
||||
{
|
||||
// Smart multi region handles dynamically change their tracking granularity based on QueryMemory calls.
|
||||
|
@ -208,7 +211,7 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
// Query some large regions to prep the subdivision of the tracking region.
|
||||
|
||||
int[] regionSizes = new int[] { 6, 4, 3, 2, 6, 1 };
|
||||
int[] regionSizes = { 6, 4, 3, 2, 6, 1 };
|
||||
ulong address = 0;
|
||||
|
||||
for (int i = 0; i < regionSizes.Length; i++)
|
||||
|
@ -242,26 +245,28 @@ namespace Ryujinx.Tests.Memory
|
|||
{
|
||||
int region = regionSizes[regionInd++];
|
||||
|
||||
Assert.AreEqual(address, expectedAddress);
|
||||
Assert.AreEqual(size, (ulong)(PageSize * region));
|
||||
Assert.Equal(expectedAddress, address);
|
||||
Assert.Equal((ulong)(PageSize * region), size);
|
||||
|
||||
expectedAddress += (ulong)(PageSize * (region + 1));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DisposeMultiHandles([Values] bool smart)
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void DisposeMultiHandles(bool smart)
|
||||
{
|
||||
// Create and initialize two overlapping Multi Region Handles, with PageSize granularity.
|
||||
const int PageCount = 32;
|
||||
const int OverlapStart = 16;
|
||||
|
||||
Assert.AreEqual(0, _tracking.GetRegionCount());
|
||||
Assert.Equal(0, _tracking.GetRegionCount());
|
||||
|
||||
IMultiRegionHandle handleLow = GetGranular(smart, 0, PageSize * PageCount, PageSize);
|
||||
PreparePages(handleLow, PageCount);
|
||||
|
||||
Assert.AreEqual(PageCount, _tracking.GetRegionCount());
|
||||
Assert.Equal(PageCount, _tracking.GetRegionCount());
|
||||
|
||||
IMultiRegionHandle handleHigh = GetGranular(smart, PageSize * OverlapStart, PageSize * PageCount, PageSize);
|
||||
PreparePages(handleHigh, PageCount, PageSize * OverlapStart);
|
||||
|
@ -269,18 +274,18 @@ namespace Ryujinx.Tests.Memory
|
|||
// Combined pages (and assuming overlapStart <= pageCount) should be pageCount after overlapStart.
|
||||
int totalPages = OverlapStart + PageCount;
|
||||
|
||||
Assert.AreEqual(totalPages, _tracking.GetRegionCount());
|
||||
Assert.Equal(totalPages, _tracking.GetRegionCount());
|
||||
|
||||
handleLow.Dispose(); // After disposing one, the pages for the other remain.
|
||||
|
||||
Assert.AreEqual(PageCount, _tracking.GetRegionCount());
|
||||
Assert.Equal(PageCount, _tracking.GetRegionCount());
|
||||
|
||||
handleHigh.Dispose(); // After disposing the other, there are no pages left.
|
||||
|
||||
Assert.AreEqual(0, _tracking.GetRegionCount());
|
||||
Assert.Equal(0, _tracking.GetRegionCount());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Fact]
|
||||
public void InheritHandles()
|
||||
{
|
||||
// Test merging the following into a granular region handle:
|
||||
|
@ -333,8 +338,7 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
// Finally, create a granular handle that inherits all these handles.
|
||||
|
||||
IEnumerable<IRegionHandle>[] handleGroups = new IEnumerable<IRegionHandle>[]
|
||||
{
|
||||
IEnumerable<IRegionHandle>[] handleGroups = {
|
||||
granular.GetHandles(),
|
||||
singlePages,
|
||||
doublePages,
|
||||
|
@ -342,8 +346,7 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
MultiRegionHandle combined = _tracking.BeginGranularTracking(0, PageSize * 18, handleGroups.SelectMany((handles) => handles), PageSize, 0);
|
||||
|
||||
bool[] expectedDirty = new bool[]
|
||||
{
|
||||
bool[] expectedDirty = {
|
||||
true, true, true, // Gap.
|
||||
false, true, false, // Multi-region.
|
||||
true, true, // Gap.
|
||||
|
@ -357,19 +360,19 @@ namespace Ryujinx.Tests.Memory
|
|||
bool modified = false;
|
||||
combined.QueryModified(PageSize * (ulong)i, PageSize, (_, _) => { modified = true; });
|
||||
|
||||
Assert.AreEqual(expectedDirty[i], modified);
|
||||
Assert.Equal(expectedDirty[i], modified);
|
||||
}
|
||||
|
||||
Assert.AreEqual(new bool[3], actionsTriggered);
|
||||
Assert.Equal(new bool[3], actionsTriggered);
|
||||
|
||||
_tracking.VirtualMemoryEvent(PageSize * 5, PageSize, false);
|
||||
Assert.IsTrue(actionsTriggered[0]);
|
||||
Assert.True(actionsTriggered[0]);
|
||||
|
||||
_tracking.VirtualMemoryEvent(PageSize * 10, PageSize, false);
|
||||
Assert.IsTrue(actionsTriggered[1]);
|
||||
Assert.True(actionsTriggered[1]);
|
||||
|
||||
_tracking.VirtualMemoryEvent(PageSize * 15, PageSize, false);
|
||||
Assert.IsTrue(actionsTriggered[2]);
|
||||
Assert.True(actionsTriggered[2]);
|
||||
|
||||
// The double page handles should be disposed, as they were split into granular handles.
|
||||
foreach (RegionHandle doublePage in doublePages)
|
||||
|
@ -386,21 +389,21 @@ namespace Ryujinx.Tests.Memory
|
|||
throws = true;
|
||||
}
|
||||
|
||||
Assert.IsTrue(throws);
|
||||
Assert.True(throws);
|
||||
}
|
||||
|
||||
IEnumerable<IRegionHandle> combinedHandles = combined.GetHandles();
|
||||
|
||||
Assert.AreEqual(handleGroups[0].ElementAt(0), combinedHandles.ElementAt(3));
|
||||
Assert.AreEqual(handleGroups[0].ElementAt(1), combinedHandles.ElementAt(4));
|
||||
Assert.AreEqual(handleGroups[0].ElementAt(2), combinedHandles.ElementAt(5));
|
||||
Assert.Equal(handleGroups[0].ElementAt(0), combinedHandles.ElementAt(3));
|
||||
Assert.Equal(handleGroups[0].ElementAt(1), combinedHandles.ElementAt(4));
|
||||
Assert.Equal(handleGroups[0].ElementAt(2), combinedHandles.ElementAt(5));
|
||||
|
||||
Assert.AreEqual(singlePages[0], combinedHandles.ElementAt(8));
|
||||
Assert.AreEqual(singlePages[1], combinedHandles.ElementAt(9));
|
||||
Assert.AreEqual(singlePages[2], combinedHandles.ElementAt(10));
|
||||
Assert.Equal(singlePages[0], combinedHandles.ElementAt(8));
|
||||
Assert.Equal(singlePages[1], combinedHandles.ElementAt(9));
|
||||
Assert.Equal(singlePages[2], combinedHandles.ElementAt(10));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Fact]
|
||||
public void PreciseAction()
|
||||
{
|
||||
bool actionTriggered = false;
|
||||
|
@ -413,11 +416,11 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
// Precise write to first handle in the multiregion.
|
||||
_tracking.VirtualMemoryEvent(PageSize * 3, PageSize, true, precise: true);
|
||||
Assert.IsFalse(actionTriggered); // Action not triggered.
|
||||
Assert.False(actionTriggered); // Action not triggered.
|
||||
|
||||
bool firstPageModified = false;
|
||||
granular.QueryModified(PageSize * 3, PageSize, (_, _) => { firstPageModified = true; });
|
||||
Assert.IsTrue(firstPageModified); // First page is modified.
|
||||
Assert.True(firstPageModified); // First page is modified.
|
||||
|
||||
// Precise write to all handles in the multiregion.
|
||||
_tracking.VirtualMemoryEvent(PageSize * 3, PageSize * 3, true, precise: true);
|
||||
|
@ -430,10 +433,10 @@ namespace Ryujinx.Tests.Memory
|
|||
granular.QueryModified(PageSize * (ulong)i, PageSize, (_, _) => { pagesModified[index] = true; });
|
||||
}
|
||||
|
||||
Assert.IsTrue(actionTriggered); // Action triggered.
|
||||
Assert.True(actionTriggered); // Action triggered.
|
||||
|
||||
// Precise writes are ignored on two later handles due to the action returning true.
|
||||
Assert.AreEqual(pagesModified, new bool[] { true, false, false });
|
||||
Assert.Equal(new[] { true, false, false }, pagesModified);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
27
src/Ryujinx.Tests.Memory/RandomRangeUL2TheoryData.cs
Normal file
27
src/Ryujinx.Tests.Memory/RandomRangeUL2TheoryData.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Ryujinx.Tests.Memory
|
||||
{
|
||||
public class RandomRangeUL2TheoryData : TheoryData<ulong, ulong>
|
||||
{
|
||||
public RandomRangeUL2TheoryData(ulong from, ulong to, int count)
|
||||
{
|
||||
byte[] buffer = new byte[8];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ulong[] results = new ulong[2];
|
||||
|
||||
for (int j = 0; j < results.Length; j++)
|
||||
{
|
||||
Random.Shared.NextBytes(buffer);
|
||||
// NOTE: The result won't be perfectly random, but it should be random enough for tests
|
||||
results[j] = BitConverter.ToUInt64(buffer) % (to + 1 - from) + from;
|
||||
}
|
||||
|
||||
Add(results[0], results[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +1,44 @@
|
|||
using NUnit.Framework;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Xunit;
|
||||
|
||||
namespace Ryujinx.Tests.Memory
|
||||
{
|
||||
public class Tests
|
||||
public class Tests : IDisposable
|
||||
{
|
||||
private static readonly ulong _memorySize = MemoryBlock.GetPageSize() * 8;
|
||||
|
||||
private MemoryBlock _memoryBlock;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
public Tests()
|
||||
{
|
||||
_memoryBlock = new MemoryBlock(_memorySize);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
_memoryBlock.Dispose();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Fact]
|
||||
public void Test_Read()
|
||||
{
|
||||
Marshal.WriteInt32(_memoryBlock.Pointer, 0x2020, 0x1234abcd);
|
||||
|
||||
Assert.AreEqual(_memoryBlock.Read<int>(0x2020), 0x1234abcd);
|
||||
Assert.Equal(0x1234abcd, _memoryBlock.Read<int>(0x2020));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Fact]
|
||||
public void Test_Write()
|
||||
{
|
||||
_memoryBlock.Write(0x2040, 0xbadc0de);
|
||||
|
||||
Assert.AreEqual(Marshal.ReadInt32(_memoryBlock.Pointer, 0x2040), 0xbadc0de);
|
||||
Assert.Equal(0xbadc0de, Marshal.ReadInt32(_memoryBlock.Pointer, 0x2040));
|
||||
}
|
||||
|
||||
[Test]
|
||||
// Memory aliasing tests fail on CI at the moment.
|
||||
[Platform(Exclude = "MacOsX")]
|
||||
[Fact]
|
||||
public void Test_Alias()
|
||||
{
|
||||
ulong pageSize = MemoryBlock.GetPageSize();
|
||||
|
@ -54,14 +51,15 @@ namespace Ryujinx.Tests.Memory
|
|||
toAlias.UnmapView(backing, pageSize * 3, pageSize);
|
||||
|
||||
toAlias.Write(0, 0xbadc0de);
|
||||
Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, (int)pageSize), 0xbadc0de);
|
||||
Assert.Equal(0xbadc0de, Marshal.ReadInt32(backing.Pointer, (int)pageSize));
|
||||
}
|
||||
|
||||
[Test]
|
||||
// Memory aliasing tests fail on CI at the moment.
|
||||
[Platform(Exclude = "MacOsX")]
|
||||
[Fact]
|
||||
public void Test_AliasRandom()
|
||||
{
|
||||
// Memory aliasing tests fail on CI at the moment.
|
||||
Skip.If(OperatingSystem.IsMacOS());
|
||||
|
||||
ulong pageSize = MemoryBlock.GetPageSize();
|
||||
int pageBits = (int)ulong.Log2(pageSize);
|
||||
ulong blockSize = MemoryBlock.GetPageSize() * 128;
|
||||
|
@ -84,7 +82,7 @@ namespace Ryujinx.Tests.Memory
|
|||
int offset = rng.Next(0, (int)pageSize - sizeof(int));
|
||||
|
||||
toAlias.Write((ulong)((dstPage << pageBits) + offset), 0xbadc0de);
|
||||
Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, (srcPage << pageBits) + offset), 0xbadc0de);
|
||||
Assert.Equal(0xbadc0de, Marshal.ReadInt32(backing.Pointer, (srcPage << pageBits) + offset));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -93,11 +91,12 @@ namespace Ryujinx.Tests.Memory
|
|||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
// Memory aliasing tests fail on CI at the moment.
|
||||
[Platform(Exclude = "MacOsX")]
|
||||
[Fact]
|
||||
public void Test_AliasMapLeak()
|
||||
{
|
||||
// Memory aliasing tests fail on CI at the moment.
|
||||
Skip.If(OperatingSystem.IsMacOS());
|
||||
|
||||
ulong pageSize = MemoryBlock.GetPageSize();
|
||||
ulong size = 100000 * pageSize; // The mappings limit on Linux is usually around 65K, so let's make sure we are above that.
|
||||
|
||||
|
@ -109,7 +108,7 @@ namespace Ryujinx.Tests.Memory
|
|||
toAlias.MapView(backing, 0, offset, pageSize);
|
||||
|
||||
toAlias.Write(offset, 0xbadc0de);
|
||||
Assert.AreEqual(0xbadc0de, backing.Read<int>(0));
|
||||
Assert.Equal(0xbadc0de, backing.Read<int>(0));
|
||||
|
||||
toAlias.UnmapView(backing, offset, pageSize);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
using NUnit.Framework;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Xunit;
|
||||
|
||||
namespace Ryujinx.Tests.Memory
|
||||
{
|
||||
public class TrackingTests
|
||||
public class TrackingTests : IDisposable
|
||||
{
|
||||
private const int RndCnt = 3;
|
||||
|
||||
|
@ -19,17 +19,16 @@ namespace Ryujinx.Tests.Memory
|
|||
private MemoryTracking _tracking;
|
||||
private MockVirtualMemoryManager _memoryManager;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
public TrackingTests()
|
||||
{
|
||||
_memoryBlock = new MemoryBlock(MemorySize);
|
||||
_memoryManager = new MockVirtualMemoryManager(MemorySize, PageSize);
|
||||
_tracking = new MemoryTracking(_memoryManager, PageSize);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
_memoryBlock.Dispose();
|
||||
}
|
||||
|
||||
|
@ -42,7 +41,7 @@ namespace Ryujinx.Tests.Memory
|
|||
return handle.Dirty;
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Fact]
|
||||
public void SingleRegion()
|
||||
{
|
||||
RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
|
||||
|
@ -66,13 +65,13 @@ namespace Ryujinx.Tests.Memory
|
|||
bool dirtyAfterUnrelatedReadWrite = handle.Dirty;
|
||||
Assert.False(dirtyAfterUnrelatedReadWrite); // Not dirtied, as the write was to an unrelated address.
|
||||
|
||||
Assert.IsNull(readTrackingTriggered); // Hasn't been triggered yet
|
||||
Assert.Null(readTrackingTriggered); // Hasn't been triggered yet
|
||||
|
||||
_tracking.VirtualMemoryEvent(0, 4, false);
|
||||
|
||||
bool dirtyAfterRelatedRead = handle.Dirty;
|
||||
Assert.False(dirtyAfterRelatedRead); // Only triggers on write.
|
||||
Assert.AreEqual(readTrackingTriggered, (0UL, 4UL)); // Read action was triggered.
|
||||
Assert.Equal(readTrackingTriggered, (0UL, 4UL)); // Read action was triggered.
|
||||
|
||||
readTrackingTriggered = null;
|
||||
_tracking.VirtualMemoryEvent(0, 4, true);
|
||||
|
@ -95,7 +94,7 @@ namespace Ryujinx.Tests.Memory
|
|||
Assert.False(dirtyAfterDispose); // Handle cannot be triggered when disposed
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Fact]
|
||||
public void OverlappingRegions()
|
||||
{
|
||||
RegionHandle allHandle = _tracking.BeginTracking(0, PageSize * 16, 0);
|
||||
|
@ -127,7 +126,7 @@ namespace Ryujinx.Tests.Memory
|
|||
{
|
||||
// No handles are dirty.
|
||||
Assert.False(allHandle.Dirty);
|
||||
Assert.IsNull(readTrackingTriggeredAll);
|
||||
Assert.Null(readTrackingTriggeredAll);
|
||||
for (int j = 0; j < 16; j++)
|
||||
{
|
||||
Assert.False(containedHandles[j].Dirty);
|
||||
|
@ -137,7 +136,7 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
// Only the handle covering the entire range and the relevant contained handle are dirty.
|
||||
Assert.True(allHandle.Dirty);
|
||||
Assert.AreEqual(readTrackingTriggeredAll, ((ulong)i * PageSize, 1UL)); // Triggered read tracking
|
||||
Assert.Equal(readTrackingTriggeredAll, ((ulong)i * PageSize, 1UL)); // Triggered read tracking
|
||||
for (int j = 0; j < 16; j++)
|
||||
{
|
||||
if (j == i)
|
||||
|
@ -157,10 +156,16 @@ namespace Ryujinx.Tests.Memory
|
|||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PageAlignment(
|
||||
[Values(1ul, 512ul, 2048ul, 4096ul, 65536ul)][Random(1ul, 65536ul, RndCnt)] ulong address,
|
||||
[Values(1ul, 4ul, 1024ul, 4096ul, 65536ul)][Random(1ul, 65536ul, RndCnt)] ulong size)
|
||||
public static readonly RandomRangeUL2TheoryData TestDataPageAlignment = new(1ul, 65536ul, RndCnt);
|
||||
|
||||
[Theory]
|
||||
[InlineData(1ul, 1ul)]
|
||||
[InlineData(512ul, 4ul)]
|
||||
[InlineData(2048ul, 1024ul)]
|
||||
[InlineData(4096ul, 4096ul)]
|
||||
[InlineData(65536ul, 65536ul)]
|
||||
[MemberData(nameof(TestDataPageAlignment))]
|
||||
public void PageAlignment(ulong address, ulong size)
|
||||
{
|
||||
ulong alignedStart = (address / PageSize) * PageSize;
|
||||
ulong alignedEnd = ((address + size + PageSize - 1) / PageSize) * PageSize;
|
||||
|
@ -191,7 +196,8 @@ namespace Ryujinx.Tests.Memory
|
|||
Assert.False(alignedAfterTriggers);
|
||||
}
|
||||
|
||||
[Test, Explicit, Timeout(1000)]
|
||||
// This test used to be skipped unless explicitly executed
|
||||
[Fact(Timeout = 1000)]
|
||||
public void Multithreading()
|
||||
{
|
||||
// Multithreading sanity test
|
||||
|
@ -287,12 +293,12 @@ namespace Ryujinx.Tests.Memory
|
|||
thread.Join();
|
||||
}
|
||||
|
||||
Assert.Greater(dirtyFlagReprotects, 10);
|
||||
Assert.Greater(writeTriggers, 10);
|
||||
Assert.Greater(handleLifecycles, 10);
|
||||
Assert.True(dirtyFlagReprotects > 10);
|
||||
Assert.True(writeTriggers >= 10);
|
||||
Assert.True(handleLifecycles >= 10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Fact]
|
||||
public void ReadActionThreadConsumption()
|
||||
{
|
||||
// Read actions should only be triggered once for each registration.
|
||||
|
@ -354,10 +360,10 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
// The action should trigger exactly once for every registration,
|
||||
// then we register once after all the threads signalling it cease.
|
||||
Assert.AreEqual(registeredCount, triggeredCount + 1);
|
||||
Assert.Equal(registeredCount, triggeredCount + 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Fact]
|
||||
public void DisposeHandles()
|
||||
{
|
||||
// Ensure that disposed handles correctly remove their virtual and physical regions.
|
||||
|
@ -365,11 +371,11 @@ namespace Ryujinx.Tests.Memory
|
|||
RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
|
||||
handle.Reprotect();
|
||||
|
||||
Assert.AreEqual(1, _tracking.GetRegionCount());
|
||||
Assert.Equal(1, _tracking.GetRegionCount());
|
||||
|
||||
handle.Dispose();
|
||||
|
||||
Assert.AreEqual(0, _tracking.GetRegionCount());
|
||||
Assert.Equal(0, _tracking.GetRegionCount());
|
||||
|
||||
// Two handles, small entirely contains big.
|
||||
// We expect there to be three regions after creating both, one for the small region and two covering the big one around it.
|
||||
|
@ -378,33 +384,33 @@ namespace Ryujinx.Tests.Memory
|
|||
RegionHandle handleSmall = _tracking.BeginTracking(PageSize, PageSize, 0);
|
||||
RegionHandle handleBig = _tracking.BeginTracking(0, PageSize * 4, 0);
|
||||
|
||||
Assert.AreEqual(3, _tracking.GetRegionCount());
|
||||
Assert.Equal(3, _tracking.GetRegionCount());
|
||||
|
||||
// After disposing the big region, only the small one will remain.
|
||||
handleBig.Dispose();
|
||||
|
||||
Assert.AreEqual(1, _tracking.GetRegionCount());
|
||||
Assert.Equal(1, _tracking.GetRegionCount());
|
||||
|
||||
handleSmall.Dispose();
|
||||
|
||||
Assert.AreEqual(0, _tracking.GetRegionCount());
|
||||
Assert.Equal(0, _tracking.GetRegionCount());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Fact]
|
||||
public void ReadAndWriteProtection()
|
||||
{
|
||||
MemoryPermission protection = MemoryPermission.ReadAndWrite;
|
||||
|
||||
_memoryManager.OnProtect += (va, size, newProtection) =>
|
||||
{
|
||||
Assert.AreEqual((0, PageSize), (va, size)); // Should protect the exact region all the operations use.
|
||||
Assert.Equal((0ul, (ulong)PageSize), (va, size)); // Should protect the exact region all the operations use.
|
||||
protection = newProtection;
|
||||
};
|
||||
|
||||
RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
|
||||
|
||||
// After creating the handle, there is no protection yet.
|
||||
Assert.AreEqual(MemoryPermission.ReadAndWrite, protection);
|
||||
Assert.Equal(MemoryPermission.ReadAndWrite, protection);
|
||||
|
||||
bool dirtyInitial = handle.Dirty;
|
||||
Assert.True(dirtyInitial); // Handle starts dirty.
|
||||
|
@ -412,7 +418,7 @@ namespace Ryujinx.Tests.Memory
|
|||
handle.Reprotect();
|
||||
|
||||
// After a reprotect, there is write protection, which will set a dirty flag when any write happens.
|
||||
Assert.AreEqual(MemoryPermission.Read, protection);
|
||||
Assert.Equal(MemoryPermission.Read, protection);
|
||||
|
||||
(ulong address, ulong size)? readTrackingTriggered = null;
|
||||
handle.RegisterAction((address, size) =>
|
||||
|
@ -421,7 +427,7 @@ namespace Ryujinx.Tests.Memory
|
|||
});
|
||||
|
||||
// Registering an action adds read/write protection.
|
||||
Assert.AreEqual(MemoryPermission.None, protection);
|
||||
Assert.Equal(MemoryPermission.None, protection);
|
||||
|
||||
bool dirtyAfterReprotect = handle.Dirty;
|
||||
Assert.False(dirtyAfterReprotect); // Handle is no longer dirty.
|
||||
|
@ -433,9 +439,9 @@ namespace Ryujinx.Tests.Memory
|
|||
bool dirtyAfterRead = handle.Dirty;
|
||||
Assert.False(dirtyAfterRead); // Not dirtied, as this was a read.
|
||||
|
||||
Assert.AreEqual(readTrackingTriggered, (0UL, 4UL)); // Read action was triggered.
|
||||
Assert.Equal(readTrackingTriggered, (0UL, 4UL)); // Read action was triggered.
|
||||
|
||||
Assert.AreEqual(MemoryPermission.Read, protection); // Write protection is still present.
|
||||
Assert.Equal(MemoryPermission.Read, protection); // Write protection is still present.
|
||||
|
||||
readTrackingTriggered = null;
|
||||
|
||||
|
@ -446,14 +452,14 @@ namespace Ryujinx.Tests.Memory
|
|||
bool dirtyAfterWriteAfterRead = handle.Dirty;
|
||||
Assert.True(dirtyAfterWriteAfterRead); // Should be dirty.
|
||||
|
||||
Assert.AreEqual(MemoryPermission.ReadAndWrite, protection); // All protection is now be removed from the memory.
|
||||
Assert.Equal(MemoryPermission.ReadAndWrite, protection); // All protection is now be removed from the memory.
|
||||
|
||||
Assert.IsNull(readTrackingTriggered); // Read tracking was removed when the action fired, as it can only fire once.
|
||||
Assert.Null(readTrackingTriggered); // Read tracking was removed when the action fired, as it can only fire once.
|
||||
|
||||
handle.Dispose();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Fact]
|
||||
public void PreciseAction()
|
||||
{
|
||||
RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
|
||||
|
@ -476,22 +482,22 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
_tracking.VirtualMemoryEvent(0, 4, false, precise: true);
|
||||
|
||||
Assert.IsNull(readTrackingTriggered); // Hasn't been triggered - precise action returned true.
|
||||
Assert.AreEqual(preciseTriggered, (0UL, 4UL, false)); // Precise action was triggered.
|
||||
Assert.Null(readTrackingTriggered); // Hasn't been triggered - precise action returned true.
|
||||
Assert.Equal(preciseTriggered, (0UL, 4UL, false)); // Precise action was triggered.
|
||||
|
||||
_tracking.VirtualMemoryEvent(0, 4, true, precise: true);
|
||||
|
||||
Assert.IsNull(readTrackingTriggered); // Still hasn't been triggered.
|
||||
Assert.Null(readTrackingTriggered); // Still hasn't been triggered.
|
||||
bool dirtyAfterPreciseActionTrue = handle.Dirty;
|
||||
Assert.False(dirtyAfterPreciseActionTrue); // Not dirtied - precise action returned true.
|
||||
Assert.AreEqual(preciseTriggered, (0UL, 4UL, true)); // Precise action was triggered.
|
||||
Assert.Equal(preciseTriggered, (0UL, 4UL, true)); // Precise action was triggered.
|
||||
|
||||
// Handle is now dirty.
|
||||
handle.Reprotect(true);
|
||||
preciseTriggered = null;
|
||||
|
||||
_tracking.VirtualMemoryEvent(4, 4, true, precise: true);
|
||||
Assert.AreEqual(preciseTriggered, (4UL, 4UL, true)); // Precise action was triggered even though handle was dirty.
|
||||
Assert.Equal(preciseTriggered, (4UL, 4UL, true)); // Precise action was triggered even though handle was dirty.
|
||||
|
||||
handle.Reprotect();
|
||||
handle.RegisterPreciseAction((address, size, write) =>
|
||||
|
@ -503,10 +509,10 @@ namespace Ryujinx.Tests.Memory
|
|||
|
||||
_tracking.VirtualMemoryEvent(8, 4, true, precise: true);
|
||||
|
||||
Assert.AreEqual(readTrackingTriggered, (8UL, 4UL)); // Read action triggered, as precise action returned false.
|
||||
Assert.Equal(readTrackingTriggered, (8UL, 4UL)); // Read action triggered, as precise action returned false.
|
||||
bool dirtyAfterPreciseActionFalse = handle.Dirty;
|
||||
Assert.True(dirtyAfterPreciseActionFalse); // Dirtied, as precise action returned false.
|
||||
Assert.AreEqual(preciseTriggered, (8UL, 4UL, true)); // Precise action was triggered.
|
||||
Assert.Equal(preciseTriggered, (8UL, 4UL, true)); // Precise action was triggered.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue