Convert Ryujinx.Tests.Memory tests to xUnit

This commit is contained in:
TSR Berry 2023-07-08 20:11:28 +02:00
commit f0441bf28b
No known key found for this signature in database
GPG key ID: 52353C0A4CCA15E2
5 changed files with 157 additions and 122 deletions

View file

@ -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)

View file

@ -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);
}
}
}

View 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]);
}
}
}
}

View file

@ -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);
}

View file

@ -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.
}
}
}