Browse Source

Run ArrayPoolMemoryAllocatorTests in separate process,

implement BasicSerializer
af/octree-no-pixelmap
Anton Firszov 6 years ago
parent
commit
a626e3a712
  1. 1
      ImageSharp.sln
  2. 182
      tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs
  3. 87
      tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs
  4. 43
      tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs
  5. 8
      tests/NuGet.config

1
ImageSharp.sln

@ -44,6 +44,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{56801022
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
tests\Directory.Build.props = tests\Directory.Build.props tests\Directory.Build.props = tests\Directory.Build.props
tests\Directory.Build.targets = tests\Directory.Build.targets tests\Directory.Build.targets = tests\Directory.Build.targets
tests\NuGet.config = tests\NuGet.config
EndProjectSection EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{FA55F5DE-11A6-487D-ABA4-BC93A02717DD}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{FA55F5DE-11A6-487D-ABA4-BC93A02717DD}"

182
tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs

@ -6,38 +6,28 @@ using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Memory.Tests 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 public class ArrayPoolMemoryAllocatorTests
{ {
private const int MaxPooledBufferSizeInBytes = 2048; private const int MaxPooledBufferSizeInBytes = 2048;
private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2; private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2;
private MemoryAllocator MemoryAllocator { get; set; } =
new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes);
/// <summary> /// <summary>
/// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location. /// Contains SUT for in-process tests.
/// </summary> /// </summary>
private bool CheckIsRentingPooledBuffer<T>(int length) private MemoryAllocatorFixture LocalFixture { get; } = new MemoryAllocatorFixture();
where T : struct
{
IMemoryOwner<T> buffer = this.MemoryAllocator.Allocate<T>(length);
ref T ptrToPrevPosition0 = ref buffer.GetReference();
buffer.Dispose();
buffer = this.MemoryAllocator.Allocate<T>(length); /// <summary>
bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.GetReference()); /// Contains SUT for tests executed by <see cref="RemoteExecutor"/>,
buffer.Dispose(); /// recreated in each external process.
/// </summary>
return sameBuffers; private static MemoryAllocatorFixture StaticFixture { get; } = new MemoryAllocatorFixture();
}
public class BufferTests : BufferTestSuite public class BufferTests : BufferTestSuite
{ {
@ -78,21 +68,21 @@ namespace SixLabors.ImageSharp.Memory.Tests
[InlineData(MaxPooledBufferSizeInBytes - 1)] [InlineData(MaxPooledBufferSizeInBytes - 1)]
public void SmallBuffersArePooled_OfByte(int size) public void SmallBuffersArePooled_OfByte(int size)
{ {
Assert.True(this.CheckIsRentingPooledBuffer<byte>(size)); Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer<byte>(size));
} }
[Theory(Skip = "Should be executed from a separate process.")] [Theory]
[InlineData(128 * 1024 * 1024)] [InlineData(128 * 1024 * 1024)]
[InlineData(MaxPooledBufferSizeInBytes + 1)] [InlineData(MaxPooledBufferSizeInBytes + 1)]
public void LargeBuffersAreNotPooled_OfByte(int size) public void LargeBuffersAreNotPooled_OfByte(int size)
{ {
if (!TestEnvironment.Is64BitProcess) static void RunTest(string sizeStr)
{ {
// can lead to OutOfMemoryException int size = int.Parse(sizeStr);
return; StaticFixture.CheckIsRentingPooledBuffer<byte>(size);
} }
Assert.False(this.CheckIsRentingPooledBuffer<byte>(size)); RemoteExecutor.Invoke(RunTest, size.ToString()).Dispose();
} }
[Fact] [Fact]
@ -100,21 +90,15 @@ namespace SixLabors.ImageSharp.Memory.Tests
{ {
int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) - 1; int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) - 1;
Assert.True(this.CheckIsRentingPooledBuffer<LargeStruct>(count)); Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer<LargeStruct>(count));
} }
[Fact(Skip = "Should be executed from a separate process.")] [Fact]
public unsafe void LaregeBuffersAreNotPooled_OfBigValueType() public unsafe void LaregeBuffersAreNotPooled_OfBigValueType()
{ {
if (!TestEnvironment.Is64BitProcess)
{
// can lead to OutOfMemoryException
return;
}
int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) + 1; int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) + 1;
Assert.False(this.CheckIsRentingPooledBuffer<LargeStruct>(count)); Assert.False(this.LocalFixture.CheckIsRentingPooledBuffer<LargeStruct>(count));
} }
[Theory] [Theory]
@ -122,12 +106,13 @@ namespace SixLabors.ImageSharp.Memory.Tests
[InlineData(AllocationOptions.Clean)] [InlineData(AllocationOptions.Clean)]
public void CleaningRequests_AreControlledByAllocationParameter_Clean(AllocationOptions options) public void CleaningRequests_AreControlledByAllocationParameter_Clean(AllocationOptions options)
{ {
using (IMemoryOwner<int> firstAlloc = this.MemoryAllocator.Allocate<int>(42)) MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator;
using (IMemoryOwner<int> firstAlloc = memoryAllocator.Allocate<int>(42))
{ {
firstAlloc.GetSpan().Fill(666); firstAlloc.GetSpan().Fill(666);
} }
using (IMemoryOwner<int> secondAlloc = this.MemoryAllocator.Allocate<int>(42, options)) using (IMemoryOwner<int> secondAlloc = memoryAllocator.Allocate<int>(42, options))
{ {
int expected = options == AllocationOptions.Clean ? 0 : 666; int expected = options == AllocationOptions.Clean ? 0 : 666;
Assert.Equal(expected, secondAlloc.GetSpan()[0]); Assert.Equal(expected, secondAlloc.GetSpan()[0]);
@ -139,7 +124,8 @@ namespace SixLabors.ImageSharp.Memory.Tests
[InlineData(true)] [InlineData(true)]
public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive) public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive)
{ {
IMemoryOwner<int> buffer = this.MemoryAllocator.Allocate<int>(32); MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator;
IMemoryOwner<int> buffer = memoryAllocator.Allocate<int>(32);
ref int ptrToPrev0 = ref MemoryMarshal.GetReference(buffer.GetSpan()); ref int ptrToPrev0 = ref MemoryMarshal.GetReference(buffer.GetSpan());
if (!keepBufferAlive) if (!keepBufferAlive)
@ -147,9 +133,9 @@ namespace SixLabors.ImageSharp.Memory.Tests
buffer.Dispose(); buffer.Dispose();
} }
this.MemoryAllocator.ReleaseRetainedResources(); memoryAllocator.ReleaseRetainedResources();
buffer = this.MemoryAllocator.Allocate<int>(32); buffer = memoryAllocator.Allocate<int>(32);
Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.GetReference())); Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.GetReference()));
} }
@ -157,87 +143,69 @@ namespace SixLabors.ImageSharp.Memory.Tests
[Fact] [Fact]
public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed() public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed()
{ {
IMemoryOwner<int> buffer = this.MemoryAllocator.Allocate<int>(32); MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator;
this.MemoryAllocator.ReleaseRetainedResources(); IMemoryOwner<int> buffer = memoryAllocator.Allocate<int>(32);
memoryAllocator.ReleaseRetainedResources();
buffer.Dispose(); buffer.Dispose();
} }
[Fact(Skip = "Should be executed from a separate process.")] [Fact]
public void AllocationOverLargeArrayThreshold_UsesDifferentPool() public void AllocationOverLargeArrayThreshold_UsesDifferentPool()
{ {
if (!TestEnvironment.Is64BitProcess) static void RunTest()
{ {
// can lead to OutOfMemoryException const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int);
return;
}
const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); IMemoryOwner<int> small = StaticFixture.MemoryAllocator.Allocate<int>(ArrayLengthThreshold - 1);
ref int ptr2Small = ref small.GetReference();
small.Dispose();
IMemoryOwner<int> small = this.MemoryAllocator.Allocate<int>(ArrayLengthThreshold - 1); IMemoryOwner<int> large = StaticFixture.MemoryAllocator.Allocate<int>(ArrayLengthThreshold + 1);
ref int ptr2Small = ref small.GetReference();
small.Dispose();
IMemoryOwner<int> large = this.MemoryAllocator.Allocate<int>(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() public void CreateWithAggressivePooling()
{ {
if (!TestEnvironment.Is64BitProcess) static void RunTest()
{ {
// can lead to OutOfMemoryException StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling();
return; Assert.True(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(4096 * 4096));
} }
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling(); RemoteExecutor.Invoke(RunTest).Dispose();
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(4096 * 4096));
} }
[Fact(Skip = "Should be executed from a separate process.")] [Fact]
public void CreateDefault() public void CreateDefault()
{ {
if (!TestEnvironment.Is64BitProcess) static void RunTest()
{ {
// can lead to OutOfMemoryException StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault();
return;
}
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault(); Assert.False(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(2 * 4096 * 4096));
Assert.True(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(2048 * 2048));
}
Assert.False(this.CheckIsRentingPooledBuffer<Rgba32>(2 * 4096 * 4096)); RemoteExecutor.Invoke(RunTest).Dispose();
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(2048 * 2048));
} }
[Fact] [Fact]
public void CreateWithModeratePooling() public void CreateWithModeratePooling()
{ {
if (!TestEnvironment.Is64BitProcess) static void RunTest()
{ {
// can lead to OutOfMemoryException StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling();
return; Assert.False(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(2048 * 2048));
Assert.True(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(1024 * 16));
} }
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); RemoteExecutor.Invoke(RunTest).Dispose();
Assert.False(this.CheckIsRentingPooledBuffer<Rgba32>(2048 * 2048));
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(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
{
} }
[Theory] [Theory]
@ -245,7 +213,8 @@ namespace SixLabors.ImageSharp.Memory.Tests
[InlineData((int.MaxValue / SizeOfLargeStruct) + 1)] [InlineData((int.MaxValue / SizeOfLargeStruct) + 1)]
public void AllocateIncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) public void AllocateIncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length)
{ {
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => this.MemoryAllocator.Allocate<LargeStruct>(length)); ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() =>
this.LocalFixture.MemoryAllocator.Allocate<LargeStruct>(length));
Assert.Equal("length", ex.ParamName); Assert.Equal("length", ex.ParamName);
} }
@ -253,8 +222,45 @@ namespace SixLabors.ImageSharp.Memory.Tests
[InlineData(-1)] [InlineData(-1)]
public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length)
{ {
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => this.MemoryAllocator.AllocateManagedByteBuffer(length)); ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() =>
this.LocalFixture.MemoryAllocator.AllocateManagedByteBuffer(length));
Assert.Equal("length", ex.ParamName); Assert.Equal("length", ex.ParamName);
} }
private class MemoryAllocatorFixture
{
public MemoryAllocator MemoryAllocator { get; set; } =
new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes);
/// <summary>
/// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location.
/// </summary>
public bool CheckIsRentingPooledBuffer<T>(int length)
where T : struct
{
IMemoryOwner<T> buffer = MemoryAllocator.Allocate<T>(length);
ref T ptrToPrevPosition0 = ref buffer.GetReference();
buffer.Dispose();
buffer = MemoryAllocator.Allocate<T>(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
{
}
} }
} }

87
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
{
/// <summary>
/// <see cref="IXunitSerializationInfo"/>-compatible serialization for cross-process use-cases.
/// </summary>
internal class BasicSerializer : IXunitSerializationInfo
{
private readonly Dictionary<string, string> map = new Dictionary<string, string>();
public const char Separator = ':';
private string DumpToString()
{
using var ms = new MemoryStream();
using var writer = new StreamWriter(ms);
foreach (KeyValuePair<string, string> 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<T>(string dump) where T : IXunitSerializable
{
T result = Activator.CreateInstance<T>();
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<T>(string key) => (T)this.GetValue(key, typeof(T));
}
}

43
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<double>(nameof(Length));
this.Name = info.GetValue<string>(nameof(Name));
this.Lives = info.GetValue<int>(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<TestObj>(str);
Assert.Equal(obj.Length, mirror.Length);
Assert.Equal(obj.Name, mirror.Name);
Assert.Equal(obj.Lives, mirror.Lives);
}
}
}

8
tests/NuGet.config

@ -0,0 +1,8 @@
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<!-- For RemoteExecutor: -->
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
</packageSources>
</configuration>
Loading…
Cancel
Save