Browse Source

Covering ArrayPoolMemoryManager and it's buffer. Took hours, but worth it!

pull/475/head
Anton Firszov 8 years ago
parent
commit
ae521701f0
  1. 3
      src/ImageSharp/Memory/IBuffer{T}.cs
  2. 3
      src/ImageSharp/Memory/IManagedByteBuffer.cs
  3. 7
      src/ImageSharp/Memory/MemoryManager.cs
  4. 74
      tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs
  5. 285
      tests/ImageSharp.Tests/Memory/BufferTestSuite.cs
  6. 15
      tests/ImageSharp.Tests/Memory/SimpleManagedMemoryManagerTests.cs

3
src/ImageSharp/Memory/IBuffer{T}.cs

@ -1,3 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Memory

3
src/ImageSharp/Memory/IManagedByteBuffer.cs

@ -1,3 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Memory
{
/// <summary>

7
src/ImageSharp/Memory/MemoryManager.cs

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Memory
{

74
tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs

@ -1,3 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Memory
{
@ -16,39 +19,31 @@ namespace SixLabors.ImageSharp.Tests.Memory
private MemoryManager MemoryManager { get; } = new ArrayPoolMemoryManager(MaxPooledBufferSizeInBytes);
/// <summary>
/// Rent 'n' buffers -> return all -> re-rent, verify if there is at least one in common.
/// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location
/// </summary>
private bool CheckIsPooled<T>(int size)
private bool CheckIsRentingPooledBuffer<T>(int length)
where T : struct
{
IBuffer<T> buf1 = this.MemoryManager.Allocate<T>(size);
IBuffer<T> buf2 = this.MemoryManager.Allocate<T>(size);
IBuffer<T> buf3 = this.MemoryManager.Allocate<T>(size);
ref T buf1FirstPrev = ref buf1.DangerousGetPinnableReference();
ref T buf2FirstPrev = ref buf2.DangerousGetPinnableReference();
ref T buf3FirstPrev = ref buf3.DangerousGetPinnableReference();
buf1.Dispose();
buf2.Dispose();
buf3.Dispose();
buf1 = this.MemoryManager.Allocate<T>(size);
buf2 = this.MemoryManager.Allocate<T>(size);
buf3 = this.MemoryManager.Allocate<T>(size);
bool same1 = Unsafe.AreSame(ref buf1FirstPrev, ref buf1.DangerousGetPinnableReference());
bool same2 = Unsafe.AreSame(ref buf2FirstPrev, ref buf2.DangerousGetPinnableReference());
bool same3 = Unsafe.AreSame(ref buf3FirstPrev, ref buf3.DangerousGetPinnableReference());
buf1.Dispose();
buf2.Dispose();
buf3.Dispose();
IBuffer<T> buffer = this.MemoryManager.Allocate<T>(length);
ref T ptrToPrevPosition0 = ref buffer.DangerousGetPinnableReference();
buffer.Dispose();
buffer = this.MemoryManager.Allocate<T>(length);
bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.DangerousGetPinnableReference());
buffer.Dispose();
return sameBuffers;
}
return same1 || same2 || same3;
public class BufferTests : BufferTestSuite
{
public BufferTests()
: base(new ArrayPoolMemoryManager(MaxPooledBufferSizeInBytes))
{
}
}
[StructLayout(LayoutKind.Explicit, Size = MaxPooledBufferSizeInBytes / 4)]
[StructLayout(LayoutKind.Explicit, Size = MaxPooledBufferSizeInBytes / 5)]
struct LargeStruct
{
}
@ -59,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
[InlineData(MaxPooledBufferSizeInBytes - 1)]
public void SmallBuffersArePooled_OfByte(int size)
{
Assert.True(this.CheckIsPooled<byte>(size));
Assert.True(this.CheckIsRentingPooledBuffer<byte>(size));
}
@ -68,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
[InlineData(MaxPooledBufferSizeInBytes + 1)]
public void LargeBuffersAreNotPooled_OfByte(int size)
{
Assert.False(this.CheckIsPooled<byte>(size));
Assert.False(this.CheckIsRentingPooledBuffer<byte>(size));
}
[Fact]
@ -76,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
int count = MaxPooledBufferSizeInBytes / sizeof(LargeStruct) - 1;
Assert.True(this.CheckIsPooled<LargeStruct>(count));
Assert.True(this.CheckIsRentingPooledBuffer<LargeStruct>(count));
}
[Fact]
@ -84,7 +79,24 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
int count = MaxPooledBufferSizeInBytes / sizeof(LargeStruct) + 1;
Assert.False(this.CheckIsPooled<LargeStruct>(count));
Assert.False(this.CheckIsRentingPooledBuffer<LargeStruct>(count));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void CleaningRequests_AreControlledByAllocationParameter_Clean(bool clean)
{
using (IBuffer<int> firstAlloc = this.MemoryManager.Allocate<int>(42))
{
firstAlloc.Span.Fill(666);
}
using (IBuffer<int> secondAlloc = this.MemoryManager.Allocate<int>(42, clean))
{
int expected = clean ? 0 : 666;
Assert.Equal(expected, secondAlloc.Span[0]);
}
}
}
}

285
tests/ImageSharp.Tests/Memory/BufferTestSuite.cs

@ -0,0 +1,285 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Memory
{
/// <summary>
/// Inherit this class to test an <see cref="IBuffer{T}"/> implementation (provided by <see cref="MemoryManager"/>).
/// </summary>
public abstract class BufferTestSuite
{
protected BufferTestSuite(MemoryManager memoryManager)
{
this.MemoryManager = memoryManager;
}
protected MemoryManager MemoryManager { get; }
public struct CustomStruct : IEquatable<CustomStruct>
{
public long A;
public byte B;
public float C;
public CustomStruct(long a, byte b, float c)
{
this.A = a;
this.B = b;
this.C = c;
}
public bool Equals(CustomStruct other)
{
return this.A == other.A && this.B == other.B && this.C.Equals(other.C);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
return obj is CustomStruct && this.Equals((CustomStruct)obj);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = this.A.GetHashCode();
hashCode = (hashCode * 397) ^ this.B.GetHashCode();
hashCode = (hashCode * 397) ^ this.C.GetHashCode();
return hashCode;
}
}
}
public static readonly TheoryData<int> LenthValues = new TheoryData<int> { 0, 1, 7, 1023, 1024 };
[Theory]
[MemberData(nameof(LenthValues))]
public void HasCorrectLength_byte(int desiredLength)
{
this.TestHasCorrectLength<byte>(desiredLength);
}
[Theory]
[MemberData(nameof(LenthValues))]
public void HasCorrectLength_float(int desiredLength)
{
this.TestHasCorrectLength<float>(desiredLength);
}
[Theory]
[MemberData(nameof(LenthValues))]
public void HasCorrectLength_CustomStruct(int desiredLength)
{
this.TestHasCorrectLength<CustomStruct>(desiredLength);
}
private void TestHasCorrectLength<T>(int desiredLength)
where T : struct
{
using (IBuffer<T> buffer = this.MemoryManager.Allocate<T>(desiredLength))
{
Assert.Equal(desiredLength, buffer.Span.Length);
}
}
[Theory]
[MemberData(nameof(LenthValues))]
public void CanAllocateCleanBuffer_byte(int desiredLength)
{
this.TestCanAllocateCleanBuffer<byte>(desiredLength, false);
this.TestCanAllocateCleanBuffer<byte>(desiredLength, true);
}
[Theory]
[MemberData(nameof(LenthValues))]
public void CanAllocateCleanBuffer_double(int desiredLength)
{
this.TestCanAllocateCleanBuffer<double>(desiredLength);
}
[Theory]
[MemberData(nameof(LenthValues))]
public void CanAllocateCleanBuffer_CustomStruct(int desiredLength)
{
this.TestCanAllocateCleanBuffer<CustomStruct>(desiredLength);
}
private IBuffer<T> Allocate<T>(int desiredLength, bool clean, bool managedByteBuffer)
where T : struct
{
if (managedByteBuffer)
{
if (!(this.MemoryManager.AllocateManagedByteBuffer(desiredLength, clean) is IBuffer<T> buffer))
{
throw new InvalidOperationException("typeof(T) != typeof(byte)");
}
return buffer;
}
return this.MemoryManager.Allocate<T>(desiredLength, clean);
}
private void TestCanAllocateCleanBuffer<T>(int desiredLength, bool testManagedByteBuffer = false)
where T : struct, IEquatable<T>
{
ReadOnlySpan<T> expected = new T[desiredLength];
for (int i = 0; i < 10; i++)
{
using (IBuffer<T> buffer = this.Allocate<T>(desiredLength, true, testManagedByteBuffer))
{
Assert.True(buffer.Span.SequenceEqual(expected));
}
}
}
[Theory]
[MemberData(nameof(LenthValues))]
public void SpanPropertyIsAlwaysTheSame_int(int desiredLength)
{
this.TestSpanPropertyIsAlwaysTheSame<int>(desiredLength);
}
[Theory]
[MemberData(nameof(LenthValues))]
public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength)
{
this.TestSpanPropertyIsAlwaysTheSame<byte>(desiredLength, false);
this.TestSpanPropertyIsAlwaysTheSame<byte>(desiredLength, true);
}
private void TestSpanPropertyIsAlwaysTheSame<T>(int desiredLength, bool testManagedByteBuffer = false)
where T : struct
{
using (IBuffer<T> buffer = this.Allocate<T>(desiredLength, false, testManagedByteBuffer))
{
ref T a = ref buffer.Span.DangerousGetPinnableReference();
ref T b = ref buffer.Span.DangerousGetPinnableReference();
ref T c = ref buffer.Span.DangerousGetPinnableReference();
Assert.True(Unsafe.AreSame(ref a, ref b));
Assert.True(Unsafe.AreSame(ref b, ref c));
}
}
[Theory]
[MemberData(nameof(LenthValues))]
public void WriteAndReadElements_float(int desiredLength)
{
this.TestWriteAndReadElements<float>(desiredLength, x => x * 1.2f);
}
[Theory]
[MemberData(nameof(LenthValues))]
public void WriteAndReadElements_byte(int desiredLength)
{
this.TestWriteAndReadElements<byte>(desiredLength, x => (byte)(x+1), false);
this.TestWriteAndReadElements<byte>(desiredLength, x => (byte)(x + 1), true);
}
private void TestWriteAndReadElements<T>(int desiredLength, Func<int, T> getExpectedValue, bool testManagedByteBuffer = false)
where T : struct
{
using (IBuffer<T> buffer = this.Allocate<T>(desiredLength, false, testManagedByteBuffer))
{
T[] expectedVals = new T[buffer.Length()];
for (int i = 0; i < buffer.Length(); i++)
{
Span<T> span = buffer.Span;
expectedVals[i] = getExpectedValue(i);
span[i] = expectedVals[i];
}
for (int i = 0; i < buffer.Length(); i++)
{
Span<T> span = buffer.Span;
Assert.Equal(expectedVals[i], span[i]);
}
}
}
[Theory]
[MemberData(nameof(LenthValues))]
public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength)
{
this.TestIndexOutOfRangeShouldThrow<byte>(desiredLength, false);
this.TestIndexOutOfRangeShouldThrow<byte>(desiredLength, true);
}
[Theory]
[MemberData(nameof(LenthValues))]
public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength)
{
this.TestIndexOutOfRangeShouldThrow<long>(desiredLength);
}
[Theory]
[MemberData(nameof(LenthValues))]
public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength)
{
this.TestIndexOutOfRangeShouldThrow<CustomStruct>(desiredLength);
}
private T TestIndexOutOfRangeShouldThrow<T>(int desiredLength, bool testManagedByteBuffer = false)
where T : struct, IEquatable<T>
{
var dummy = default(T);
using (IBuffer<T> buffer = this.Allocate<T>(desiredLength, false, testManagedByteBuffer))
{
Assert.ThrowsAny<Exception>(
() =>
{
Span<T> span = buffer.Span;
dummy = span[desiredLength];
});
Assert.ThrowsAny<Exception>(
() =>
{
Span<T> span = buffer.Span;
dummy = span[desiredLength + 1];
});
Assert.ThrowsAny<Exception>(
() =>
{
Span<T> span = buffer.Span;
dummy = span[desiredLength + 42];
});
}
return dummy;
}
[Theory]
[InlineData(1)]
[InlineData(7)]
[InlineData(1024)]
[InlineData(6666)]
public void ManagedByteBuffer_ArrayIsCorrect(int desiredLength)
{
using (IManagedByteBuffer buffer = this.MemoryManager.AllocateManagedByteBuffer(desiredLength))
{
ref byte array0 = ref buffer.Array[0];
ref byte span0 = ref buffer.DangerousGetPinnableReference();
Assert.True(Unsafe.AreSame(ref span0, ref array0));
Assert.True(buffer.Array.Length >= buffer.Span.Length);
}
}
}
}

15
tests/ImageSharp.Tests/Memory/SimpleManagedMemoryManagerTests.cs

@ -0,0 +1,15 @@
namespace SixLabors.ImageSharp.Tests.Memory
{
using SixLabors.ImageSharp.Memory;
public class SimpleManagedMemoryManagerTests
{
public class BufferTests : BufferTestSuite
{
public BufferTests()
: base(new SimpleManagedMemoryManager())
{
}
}
}
}
Loading…
Cancel
Save