diff options
Diffstat (limited to 'src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs')
| -rw-r--r-- | src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs b/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs new file mode 100644 index 00000000..c0127530 --- /dev/null +++ b/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs @@ -0,0 +1,359 @@ +using NUnit.Framework; +using Ryujinx.Common.Extensions; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Tests.Common.Extensions +{ + public class SequenceReaderExtensionsTests + { + [TestCase(null)] + [TestCase(sizeof(int) + 1)] + public void GetRefOrRefToCopy_ReadsMultiSegmentedSequenceSuccessfully(int? maxSegmentSize) + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray(); + + ReadOnlySequence<byte> sequence = + CreateSegmentedByteSequence(originalStructs, maxSegmentSize ?? Unsafe.SizeOf<MyUnmanagedStruct>()); + + var sequenceReader = new SequenceReader<byte>(sequence); + + foreach (var original in originalStructs) + { + // Act + ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + } + } + + [Test] + public void GetRefOrRefToCopy_FragmentedSequenceReturnsRefToCopy() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, 3); + + var sequenceReader = new SequenceReader<byte>(sequence); + + foreach (var original in originalStructs) + { + // Act + ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out var copy); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + MyUnmanagedStruct.Assert(Assert.AreEqual, read, copy); + } + } + + [Test] + public void GetRefOrRefToCopy_ContiguousSequenceReturnsRefToBuffer() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue); + + var sequenceReader = new SequenceReader<byte>(sequence); + + foreach (var original in originalStructs) + { + // Act + ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out var copy); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + MyUnmanagedStruct.Assert(Assert.AreNotEqual, read, copy); + } + } + + [Test] + public void GetRefOrRefToCopy_ThrowsWhenNotEnoughData() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue); + + // Act/Assert + Assert.Throws<ArgumentOutOfRangeException>(() => + { + var sequenceReader = new SequenceReader<byte>(sequence); + + sequenceReader.Advance(1); + + ref readonly MyUnmanagedStruct result = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _); + }); + } + + [Test] + public void ReadLittleEndian_Int32_RoundTripsSuccessfully() + { + // Arrange + const int TestValue = 0x1234abcd; + + byte[] buffer = new byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32LittleEndian(buffer.AsSpan(), TestValue); + + var sequenceReader = new SequenceReader<byte>(new ReadOnlySequence<byte>(buffer)); + + // Act + sequenceReader.ReadLittleEndian(out int roundTrippedValue); + + // Assert + Assert.AreEqual(TestValue, roundTrippedValue); + } + + [Test] + public void ReadLittleEndian_Int32_ResultIsNotBigEndian() + { + // Arrange + const int TestValue = 0x1234abcd; + + byte[] buffer = new byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue); + + var sequenceReader = new SequenceReader<byte>(new ReadOnlySequence<byte>(buffer)); + + // Act + sequenceReader.ReadLittleEndian(out int roundTrippedValue); + + // Assert + Assert.AreNotEqual(TestValue, roundTrippedValue); + } + + [Test] + public void ReadLittleEndian_Int32_ThrowsWhenNotEnoughData() + { + // Arrange + const int TestValue = 0x1234abcd; + + byte[] buffer = new byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue); + + // Act/Assert + Assert.Throws<ArgumentOutOfRangeException>(() => + { + var sequenceReader = new SequenceReader<byte>(new ReadOnlySequence<byte>(buffer)); + sequenceReader.Advance(1); + + sequenceReader.ReadLittleEndian(out int roundTrippedValue); + }); + } + + [Test] + public void ReadUnmanaged_ContiguousSequence_Succeeds() + => ReadUnmanaged_Succeeds(int.MaxValue); + + [Test] + public void ReadUnmanaged_FragmentedSequence_Succeeds() + => ReadUnmanaged_Succeeds(sizeof(int) + 1); + + [Test] + public void ReadUnmanaged_ThrowsWhenNotEnoughData() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue); + + // Act/Assert + Assert.Throws<ArgumentOutOfRangeException>(() => + { + var sequenceReader = new SequenceReader<byte>(sequence); + + sequenceReader.Advance(1); + + sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read); + }); + } + + [Test] + public void SetConsumed_ContiguousSequence_SucceedsWhenValid() + => SetConsumed_SucceedsWhenValid(int.MaxValue); + + [Test] + public void SetConsumed_FragmentedSequence_SucceedsWhenValid() + => SetConsumed_SucceedsWhenValid(sizeof(int) + 1); + + [Test] + public void SetConsumed_ThrowsWhenBeyondActualLength() + { + const int StructCount = 2; + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(StructCount).ToArray(); + + ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, MyUnmanagedStruct.SizeOf); + + Assert.Throws<ArgumentOutOfRangeException>(() => + { + var sequenceReader = new SequenceReader<byte>(sequence); + + sequenceReader.SetConsumed(MyUnmanagedStruct.SizeOf * StructCount + 1); + }); + } + + private static void ReadUnmanaged_Succeeds(int maxSegmentLength) + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray(); + + ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength); + + var sequenceReader = new SequenceReader<byte>(sequence); + + foreach (var original in originalStructs) + { + // Act + sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + } + } + + private static void SetConsumed_SucceedsWhenValid(int maxSegmentLength) + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(2).ToArray(); + + ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength); + + var sequenceReader = new SequenceReader<byte>(sequence); + + static void SetConsumedAndAssert(scoped ref SequenceReader<byte> sequenceReader, long consumed) + { + sequenceReader.SetConsumed(consumed); + Assert.AreEqual(consumed, sequenceReader.Consumed); + } + + // Act/Assert + ref readonly MyUnmanagedStruct struct0A = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _); + + Assert.AreEqual(sequenceReader.Consumed, MyUnmanagedStruct.SizeOf); + + SetConsumedAndAssert(ref sequenceReader, 0); + + ref readonly MyUnmanagedStruct struct0B = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _); + + MyUnmanagedStruct.Assert(Assert.AreEqual, struct0A, struct0B); + + SetConsumedAndAssert(ref sequenceReader, 1); + + SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf); + + ref readonly MyUnmanagedStruct struct1A = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _); + + SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf); + + ref readonly MyUnmanagedStruct struct1B = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _); + + MyUnmanagedStruct.Assert(Assert.AreEqual, struct1A, struct1B); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct MyUnmanagedStruct + { + public int BehaviourSize; + public int MemoryPoolsSize; + public short VoicesSize; + public int VoiceResourcesSize; + public short EffectsSize; + public int RenderInfoSize; + + public unsafe fixed byte Reserved[16]; + + public static readonly int SizeOf = Unsafe.SizeOf<MyUnmanagedStruct>(); + + public static unsafe MyUnmanagedStruct Generate(Random rng) + { + const int BaseInt32Value = 0x1234abcd; + const short BaseInt16Value = 0x5678; + + var result = new MyUnmanagedStruct + { + BehaviourSize = BaseInt32Value ^ rng.Next(), + MemoryPoolsSize = BaseInt32Value ^ rng.Next(), + VoicesSize = (short)(BaseInt16Value ^ rng.Next()), + VoiceResourcesSize = BaseInt32Value ^ rng.Next(), + EffectsSize = (short)(BaseInt16Value ^ rng.Next()), + RenderInfoSize = BaseInt32Value ^ rng.Next(), + }; + + Unsafe.Write(result.Reserved, rng.NextInt64()); + + return result; + } + + public static unsafe void Assert(Action<object, object> assert, in MyUnmanagedStruct expected, in MyUnmanagedStruct actual) + { + assert(expected.BehaviourSize, actual.BehaviourSize); + assert(expected.MemoryPoolsSize, actual.MemoryPoolsSize); + assert(expected.VoicesSize, actual.VoicesSize); + assert(expected.VoiceResourcesSize, actual.VoiceResourcesSize); + assert(expected.EffectsSize, actual.EffectsSize); + assert(expected.RenderInfoSize, actual.RenderInfoSize); + + fixed (void* expectedReservedPtr = expected.Reserved) + fixed (void* actualReservedPtr = actual.Reserved) + { + long expectedReservedLong = Unsafe.Read<long>(expectedReservedPtr); + long actualReservedLong = Unsafe.Read<long>(actualReservedPtr); + + assert(expectedReservedLong, actualReservedLong); + } + } + } + + private static IEnumerable<MyUnmanagedStruct> EnumerateNewUnmanagedStructs() + { + var rng = new Random(0); + + while (true) + { + yield return MyUnmanagedStruct.Generate(rng); + } + } + + private static ReadOnlySequence<byte> CreateSegmentedByteSequence<T>(T[] array, int maxSegmentLength) where T : unmanaged + { + byte[] arrayBytes = MemoryMarshal.AsBytes(array.AsSpan()).ToArray(); + var memory = new Memory<byte>(arrayBytes); + int index = 0; + + BytesReadOnlySequenceSegment first = null, last = null; + + while (index < memory.Length) + { + int nextSegmentLength = Math.Min(maxSegmentLength, memory.Length - index); + var nextSegment = memory.Slice(index, nextSegmentLength); + + if (first == null) + { + first = last = new BytesReadOnlySequenceSegment(nextSegment); + } + else + { + last = last.Append(nextSegment); + } + + index += nextSegmentLength; + } + + return new ReadOnlySequence<byte>(first, 0, last, (int)(memory.Length - last.RunningIndex)); + } + } +} |
