diff --git a/ImageSharp.sln b/ImageSharp.sln index 875ede1b2..a5ab1b297 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -44,6 +44,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{56801022 ProjectSection(SolutionItems) = preProject tests\Directory.Build.props = tests\Directory.Build.props tests\Directory.Build.targets = tests\Directory.Build.targets + tests\NuGet.config = tests\NuGet.config EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{FA55F5DE-11A6-487D-ABA4-BC93A02717DD}" diff --git a/tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs index d38b5b9dd..8b850b91f 100644 --- a/tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs @@ -6,38 +6,28 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Tests; using Xunit; namespace SixLabors.ImageSharp.Memory.Tests { - // TODO: Re-enable memory-intensive tests with arcade RemoteExecutor: - // https://github.com/dotnet/runtime/blob/master/docs/project/writing-tests.md#remoteexecutor public class ArrayPoolMemoryAllocatorTests { private const int MaxPooledBufferSizeInBytes = 2048; private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2; - private MemoryAllocator MemoryAllocator { get; set; } = - new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes); - /// - /// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location. + /// Contains SUT for in-process tests. /// - private bool CheckIsRentingPooledBuffer(int length) - where T : struct - { - IMemoryOwner buffer = this.MemoryAllocator.Allocate(length); - ref T ptrToPrevPosition0 = ref buffer.GetReference(); - buffer.Dispose(); + private MemoryAllocatorFixture LocalFixture { get; } = new MemoryAllocatorFixture(); - buffer = this.MemoryAllocator.Allocate(length); - bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.GetReference()); - buffer.Dispose(); - - return sameBuffers; - } + /// + /// Contains SUT for tests executed by , + /// recreated in each external process. + /// + private static MemoryAllocatorFixture StaticFixture { get; } = new MemoryAllocatorFixture(); public class BufferTests : BufferTestSuite { @@ -78,21 +68,21 @@ namespace SixLabors.ImageSharp.Memory.Tests [InlineData(MaxPooledBufferSizeInBytes - 1)] public void SmallBuffersArePooled_OfByte(int size) { - Assert.True(this.CheckIsRentingPooledBuffer(size)); + Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(size)); } - [Theory(Skip = "Should be executed from a separate process.")] + [Theory] [InlineData(128 * 1024 * 1024)] [InlineData(MaxPooledBufferSizeInBytes + 1)] public void LargeBuffersAreNotPooled_OfByte(int size) { - if (!TestEnvironment.Is64BitProcess) + static void RunTest(string sizeStr) { - // can lead to OutOfMemoryException - return; + int size = int.Parse(sizeStr); + StaticFixture.CheckIsRentingPooledBuffer(size); } - Assert.False(this.CheckIsRentingPooledBuffer(size)); + RemoteExecutor.Invoke(RunTest, size.ToString()).Dispose(); } [Fact] @@ -100,21 +90,15 @@ namespace SixLabors.ImageSharp.Memory.Tests { int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) - 1; - Assert.True(this.CheckIsRentingPooledBuffer(count)); + Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(count)); } - [Fact(Skip = "Should be executed from a separate process.")] + [Fact] public unsafe void LaregeBuffersAreNotPooled_OfBigValueType() { - if (!TestEnvironment.Is64BitProcess) - { - // can lead to OutOfMemoryException - return; - } - int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) + 1; - Assert.False(this.CheckIsRentingPooledBuffer(count)); + Assert.False(this.LocalFixture.CheckIsRentingPooledBuffer(count)); } [Theory] @@ -122,12 +106,13 @@ namespace SixLabors.ImageSharp.Memory.Tests [InlineData(AllocationOptions.Clean)] public void CleaningRequests_AreControlledByAllocationParameter_Clean(AllocationOptions options) { - using (IMemoryOwner firstAlloc = this.MemoryAllocator.Allocate(42)) + MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; + using (IMemoryOwner firstAlloc = memoryAllocator.Allocate(42)) { firstAlloc.GetSpan().Fill(666); } - using (IMemoryOwner secondAlloc = this.MemoryAllocator.Allocate(42, options)) + using (IMemoryOwner secondAlloc = memoryAllocator.Allocate(42, options)) { int expected = options == AllocationOptions.Clean ? 0 : 666; Assert.Equal(expected, secondAlloc.GetSpan()[0]); @@ -139,7 +124,8 @@ namespace SixLabors.ImageSharp.Memory.Tests [InlineData(true)] public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive) { - IMemoryOwner buffer = this.MemoryAllocator.Allocate(32); + MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; + IMemoryOwner buffer = memoryAllocator.Allocate(32); ref int ptrToPrev0 = ref MemoryMarshal.GetReference(buffer.GetSpan()); if (!keepBufferAlive) @@ -147,9 +133,9 @@ namespace SixLabors.ImageSharp.Memory.Tests buffer.Dispose(); } - this.MemoryAllocator.ReleaseRetainedResources(); + memoryAllocator.ReleaseRetainedResources(); - buffer = this.MemoryAllocator.Allocate(32); + buffer = memoryAllocator.Allocate(32); Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.GetReference())); } @@ -157,87 +143,69 @@ namespace SixLabors.ImageSharp.Memory.Tests [Fact] public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed() { - IMemoryOwner buffer = this.MemoryAllocator.Allocate(32); - this.MemoryAllocator.ReleaseRetainedResources(); + MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; + IMemoryOwner buffer = memoryAllocator.Allocate(32); + memoryAllocator.ReleaseRetainedResources(); buffer.Dispose(); } - [Fact(Skip = "Should be executed from a separate process.")] + [Fact] public void AllocationOverLargeArrayThreshold_UsesDifferentPool() { - if (!TestEnvironment.Is64BitProcess) + static void RunTest() { - // can lead to OutOfMemoryException - return; - } + const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); - const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); + IMemoryOwner small = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold - 1); + ref int ptr2Small = ref small.GetReference(); + small.Dispose(); - IMemoryOwner small = this.MemoryAllocator.Allocate(ArrayLengthThreshold - 1); - ref int ptr2Small = ref small.GetReference(); - small.Dispose(); + IMemoryOwner large = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold + 1); - IMemoryOwner large = this.MemoryAllocator.Allocate(ArrayLengthThreshold + 1); + Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.GetReference())); + } - Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.GetReference())); + RemoteExecutor.Invoke(RunTest).Dispose(); } - [Fact(Skip = "Should be executed from a separate process.")] + [Fact] public void CreateWithAggressivePooling() { - if (!TestEnvironment.Is64BitProcess) + static void RunTest() { - // can lead to OutOfMemoryException - return; + StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling(); + Assert.True(StaticFixture.CheckIsRentingPooledBuffer(4096 * 4096)); } - this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling(); - - Assert.True(this.CheckIsRentingPooledBuffer(4096 * 4096)); + RemoteExecutor.Invoke(RunTest).Dispose(); } - [Fact(Skip = "Should be executed from a separate process.")] + [Fact] public void CreateDefault() { - if (!TestEnvironment.Is64BitProcess) + static void RunTest() { - // can lead to OutOfMemoryException - return; - } + StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault(); - this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault(); + Assert.False(StaticFixture.CheckIsRentingPooledBuffer(2 * 4096 * 4096)); + Assert.True(StaticFixture.CheckIsRentingPooledBuffer(2048 * 2048)); + } - Assert.False(this.CheckIsRentingPooledBuffer(2 * 4096 * 4096)); - Assert.True(this.CheckIsRentingPooledBuffer(2048 * 2048)); + RemoteExecutor.Invoke(RunTest).Dispose(); } [Fact] public void CreateWithModeratePooling() { - if (!TestEnvironment.Is64BitProcess) + static void RunTest() { - // can lead to OutOfMemoryException - return; + StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); + Assert.False(StaticFixture.CheckIsRentingPooledBuffer(2048 * 2048)); + Assert.True(StaticFixture.CheckIsRentingPooledBuffer(1024 * 16)); } - this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); - - Assert.False(this.CheckIsRentingPooledBuffer(2048 * 2048)); - Assert.True(this.CheckIsRentingPooledBuffer(1024 * 16)); - } - - [StructLayout(LayoutKind.Sequential)] - private struct Rgba32 - { - private readonly uint dummy; - } - - private const int SizeOfLargeStruct = MaxPooledBufferSizeInBytes / 5; - - [StructLayout(LayoutKind.Explicit, Size = SizeOfLargeStruct)] - private struct LargeStruct - { + RemoteExecutor.Invoke(RunTest).Dispose(); } [Theory] @@ -245,7 +213,8 @@ namespace SixLabors.ImageSharp.Memory.Tests [InlineData((int.MaxValue / SizeOfLargeStruct) + 1)] public void AllocateIncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) { - ArgumentOutOfRangeException ex = Assert.Throws(() => this.MemoryAllocator.Allocate(length)); + ArgumentOutOfRangeException ex = Assert.Throws(() => + this.LocalFixture.MemoryAllocator.Allocate(length)); Assert.Equal("length", ex.ParamName); } @@ -253,8 +222,45 @@ namespace SixLabors.ImageSharp.Memory.Tests [InlineData(-1)] public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) { - ArgumentOutOfRangeException ex = Assert.Throws(() => this.MemoryAllocator.AllocateManagedByteBuffer(length)); + ArgumentOutOfRangeException ex = Assert.Throws(() => + this.LocalFixture.MemoryAllocator.AllocateManagedByteBuffer(length)); Assert.Equal("length", ex.ParamName); } + + private class MemoryAllocatorFixture + { + public MemoryAllocator MemoryAllocator { get; set; } = + new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes); + + /// + /// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location. + /// + public bool CheckIsRentingPooledBuffer(int length) + where T : struct + { + IMemoryOwner buffer = MemoryAllocator.Allocate(length); + ref T ptrToPrevPosition0 = ref buffer.GetReference(); + buffer.Dispose(); + + buffer = MemoryAllocator.Allocate(length); + bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.GetReference()); + buffer.Dispose(); + + return sameBuffers; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct SmallStruct + { + private readonly uint dummy; + } + + private const int SizeOfLargeStruct = MaxPooledBufferSizeInBytes / 5; + + [StructLayout(LayoutKind.Explicit, Size = SizeOfLargeStruct)] + private struct LargeStruct + { + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs new file mode 100644 index 000000000..971e6c54e --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.TestUtilities +{ + /// + /// -compatible serialization for cross-process use-cases. + /// + internal class BasicSerializer : IXunitSerializationInfo + { + private readonly Dictionary map = new Dictionary(); + + public const char Separator = ':'; + + private string DumpToString() + { + using var ms = new MemoryStream(); + using var writer = new StreamWriter(ms); + foreach (KeyValuePair kv in this.map) + { + writer.WriteLine($"{kv.Key}{Separator}{kv.Value}"); + } + writer.Flush(); + byte[] data = ms.ToArray(); + return System.Convert.ToBase64String(data); + } + + private void LoadDump(string dump) + { + byte[] data = System.Convert.FromBase64String(dump); + + using var ms = new MemoryStream(data); + using var reader = new StreamReader(ms); + for (string s = reader.ReadLine(); s != null ; s = reader.ReadLine()) + { + string[] kv = s.Split(Separator); + this.map[kv[0]] = kv[1]; + } + } + + public static string Serialize(IXunitSerializable serializable) + { + var serializer = new BasicSerializer(); + serializable.Serialize(serializer); + return serializer.DumpToString(); + } + + public static T Deserialize(string dump) where T : IXunitSerializable + { + T result = Activator.CreateInstance(); + var serializer = new BasicSerializer(); + serializer.LoadDump(dump); + result.Deserialize(serializer); + return result; + } + + public void AddValue(string key, object value, Type type = null) + { + Guard.NotNull(key, nameof(key)); + if (value == null) + { + return; + } + type ??= value.GetType(); + + this.map[key] = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value); + } + + public object GetValue(string key, Type type) + { + Guard.NotNull(key, nameof(key)); + + if (!this.map.TryGetValue(key, out string str)) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + + return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str); + } + + public T GetValue(string key) => (T)this.GetValue(key, typeof(T)); + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs new file mode 100644 index 000000000..ba5bdb219 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs @@ -0,0 +1,43 @@ +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests +{ + public class BasicSerializerTests + { + class TestObj : IXunitSerializable + { + public double Length { get; set; } + public string Name { get; set; } + public int Lives { get; set; } + + public void Deserialize(IXunitSerializationInfo info) + { + info.AddValue(nameof(Length), Length); + info.AddValue(nameof(Name), Name); + info.AddValue(nameof(this.Lives), Lives); + } + + public void Serialize(IXunitSerializationInfo info) + { + this.Length = info.GetValue(nameof(Length)); + this.Name = info.GetValue(nameof(Name)); + this.Lives = info.GetValue(nameof(Lives)); + } + } + + [Fact] + public void SerializeDeserialize_ShouldPreserveValues() + { + var obj = new TestObj() {Length = 123, Name = "Lol123!", Lives = 7}; + + string str = BasicSerializer.Serialize(obj); + TestObj mirror = BasicSerializer.Deserialize(str); + + Assert.Equal(obj.Length, mirror.Length); + Assert.Equal(obj.Name, mirror.Name); + Assert.Equal(obj.Lives, mirror.Lives); + } + } +} diff --git a/tests/NuGet.config b/tests/NuGet.config new file mode 100644 index 000000000..8f9bf7e10 --- /dev/null +++ b/tests/NuGet.config @@ -0,0 +1,8 @@ + + + + + + + +