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
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}"

182
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);
/// <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>
private bool CheckIsRentingPooledBuffer<T>(int length)
where T : struct
{
IMemoryOwner<T> buffer = this.MemoryAllocator.Allocate<T>(length);
ref T ptrToPrevPosition0 = ref buffer.GetReference();
buffer.Dispose();
private MemoryAllocatorFixture LocalFixture { get; } = new MemoryAllocatorFixture();
buffer = this.MemoryAllocator.Allocate<T>(length);
bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.GetReference());
buffer.Dispose();
return sameBuffers;
}
/// <summary>
/// Contains SUT for tests executed by <see cref="RemoteExecutor"/>,
/// recreated in each external process.
/// </summary>
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<byte>(size));
Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer<byte>(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<byte>(size);
}
Assert.False(this.CheckIsRentingPooledBuffer<byte>(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<LargeStruct>(count));
Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer<LargeStruct>(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<LargeStruct>(count));
Assert.False(this.LocalFixture.CheckIsRentingPooledBuffer<LargeStruct>(count));
}
[Theory]
@ -122,12 +106,13 @@ namespace SixLabors.ImageSharp.Memory.Tests
[InlineData(AllocationOptions.Clean)]
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);
}
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;
Assert.Equal(expected, secondAlloc.GetSpan()[0]);
@ -139,7 +124,8 @@ namespace SixLabors.ImageSharp.Memory.Tests
[InlineData(true)]
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());
if (!keepBufferAlive)
@ -147,9 +133,9 @@ namespace SixLabors.ImageSharp.Memory.Tests
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()));
}
@ -157,87 +143,69 @@ namespace SixLabors.ImageSharp.Memory.Tests
[Fact]
public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed()
{
IMemoryOwner<int> buffer = this.MemoryAllocator.Allocate<int>(32);
this.MemoryAllocator.ReleaseRetainedResources();
MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator;
IMemoryOwner<int> buffer = memoryAllocator.Allocate<int>(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<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);
ref int ptr2Small = ref small.GetReference();
small.Dispose();
IMemoryOwner<int> large = StaticFixture.MemoryAllocator.Allocate<int>(ArrayLengthThreshold + 1);
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()
{
if (!TestEnvironment.Is64BitProcess)
static void RunTest()
{
// can lead to OutOfMemoryException
return;
StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling();
Assert.True(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(4096 * 4096));
}
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling();
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(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<SmallStruct>(2 * 4096 * 4096));
Assert.True(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(2048 * 2048));
}
Assert.False(this.CheckIsRentingPooledBuffer<Rgba32>(2 * 4096 * 4096));
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(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<SmallStruct>(2048 * 2048));
Assert.True(StaticFixture.CheckIsRentingPooledBuffer<SmallStruct>(1024 * 16));
}
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling();
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
{
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<ArgumentOutOfRangeException>(() => this.MemoryAllocator.Allocate<LargeStruct>(length));
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() =>
this.LocalFixture.MemoryAllocator.Allocate<LargeStruct>(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<ArgumentOutOfRangeException>(() => this.MemoryAllocator.AllocateManagedByteBuffer(length));
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() =>
this.LocalFixture.MemoryAllocator.AllocateManagedByteBuffer(length));
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