From ae521701f052e993f0a88ebcdba40662617c12e2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 20 Feb 2018 19:18:33 +0100 Subject: [PATCH] Covering ArrayPoolMemoryManager and it's buffer. Took hours, but worth it! --- src/ImageSharp/Memory/IBuffer{T}.cs | 3 + src/ImageSharp/Memory/IManagedByteBuffer.cs | 3 + src/ImageSharp/Memory/MemoryManager.cs | 7 +- .../Memory/ArrayPoolMemoryManagerTests.cs | 74 +++-- .../Memory/BufferTestSuite.cs | 285 ++++++++++++++++++ .../Memory/SimpleManagedMemoryManagerTests.cs | 15 + 6 files changed, 351 insertions(+), 36 deletions(-) create mode 100644 tests/ImageSharp.Tests/Memory/BufferTestSuite.cs create mode 100644 tests/ImageSharp.Tests/Memory/SimpleManagedMemoryManagerTests.cs diff --git a/src/ImageSharp/Memory/IBuffer{T}.cs b/src/ImageSharp/Memory/IBuffer{T}.cs index a0f80063f..db6bf5b38 100644 --- a/src/ImageSharp/Memory/IBuffer{T}.cs +++ b/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 diff --git a/src/ImageSharp/Memory/IManagedByteBuffer.cs b/src/ImageSharp/Memory/IManagedByteBuffer.cs index 541957f42..d75fb9b6c 100644 --- a/src/ImageSharp/Memory/IManagedByteBuffer.cs +++ b/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 { /// diff --git a/src/ImageSharp/Memory/MemoryManager.cs b/src/ImageSharp/Memory/MemoryManager.cs index 7445c66b7..7318a313e 100644 --- a/src/ImageSharp/Memory/MemoryManager.cs +++ b/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 { diff --git a/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs b/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs index 2a6a23116..fcd4c1b3b 100644 --- a/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs +++ b/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); /// - /// 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 /// - private bool CheckIsPooled(int size) + private bool CheckIsRentingPooledBuffer(int length) where T : struct { - IBuffer buf1 = this.MemoryManager.Allocate(size); - IBuffer buf2 = this.MemoryManager.Allocate(size); - IBuffer buf3 = this.MemoryManager.Allocate(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(size); - buf2 = this.MemoryManager.Allocate(size); - buf3 = this.MemoryManager.Allocate(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 buffer = this.MemoryManager.Allocate(length); + ref T ptrToPrevPosition0 = ref buffer.DangerousGetPinnableReference(); + buffer.Dispose(); + + buffer = this.MemoryManager.Allocate(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(size)); + Assert.True(this.CheckIsRentingPooledBuffer(size)); } @@ -68,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Memory [InlineData(MaxPooledBufferSizeInBytes + 1)] public void LargeBuffersAreNotPooled_OfByte(int size) { - Assert.False(this.CheckIsPooled(size)); + Assert.False(this.CheckIsRentingPooledBuffer(size)); } [Fact] @@ -76,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { int count = MaxPooledBufferSizeInBytes / sizeof(LargeStruct) - 1; - Assert.True(this.CheckIsPooled(count)); + Assert.True(this.CheckIsRentingPooledBuffer(count)); } [Fact] @@ -84,7 +79,24 @@ namespace SixLabors.ImageSharp.Tests.Memory { int count = MaxPooledBufferSizeInBytes / sizeof(LargeStruct) + 1; - Assert.False(this.CheckIsPooled(count)); + Assert.False(this.CheckIsRentingPooledBuffer(count)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CleaningRequests_AreControlledByAllocationParameter_Clean(bool clean) + { + using (IBuffer firstAlloc = this.MemoryManager.Allocate(42)) + { + firstAlloc.Span.Fill(666); + } + + using (IBuffer secondAlloc = this.MemoryManager.Allocate(42, clean)) + { + int expected = clean ? 0 : 666; + Assert.Equal(expected, secondAlloc.Span[0]); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/BufferTestSuite.cs new file mode 100644 index 000000000..50477cb5c --- /dev/null +++ b/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 +{ + + + /// + /// Inherit this class to test an implementation (provided by ). + /// + public abstract class BufferTestSuite + { + protected BufferTestSuite(MemoryManager memoryManager) + { + this.MemoryManager = memoryManager; + } + + protected MemoryManager MemoryManager { get; } + + public struct CustomStruct : IEquatable + { + 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 LenthValues = new TheoryData { 0, 1, 7, 1023, 1024 }; + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_byte(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_float(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_CustomStruct(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + private void TestHasCorrectLength(int desiredLength) + where T : struct + { + using (IBuffer buffer = this.MemoryManager.Allocate(desiredLength)) + { + Assert.Equal(desiredLength, buffer.Span.Length); + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_byte(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength, false); + this.TestCanAllocateCleanBuffer(desiredLength, true); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_double(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_CustomStruct(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } + + private IBuffer Allocate(int desiredLength, bool clean, bool managedByteBuffer) + where T : struct + { + if (managedByteBuffer) + { + if (!(this.MemoryManager.AllocateManagedByteBuffer(desiredLength, clean) is IBuffer buffer)) + { + throw new InvalidOperationException("typeof(T) != typeof(byte)"); + } + + return buffer; + } + + return this.MemoryManager.Allocate(desiredLength, clean); + } + + private void TestCanAllocateCleanBuffer(int desiredLength, bool testManagedByteBuffer = false) + where T : struct, IEquatable + { + ReadOnlySpan expected = new T[desiredLength]; + + for (int i = 0; i < 10; i++) + { + using (IBuffer buffer = this.Allocate(desiredLength, true, testManagedByteBuffer)) + { + Assert.True(buffer.Span.SequenceEqual(expected)); + } + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void SpanPropertyIsAlwaysTheSame_int(int desiredLength) + { + this.TestSpanPropertyIsAlwaysTheSame(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength) + { + this.TestSpanPropertyIsAlwaysTheSame(desiredLength, false); + this.TestSpanPropertyIsAlwaysTheSame(desiredLength, true); + } + + private void TestSpanPropertyIsAlwaysTheSame(int desiredLength, bool testManagedByteBuffer = false) + where T : struct + { + using (IBuffer buffer = this.Allocate(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(desiredLength, x => x * 1.2f); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void WriteAndReadElements_byte(int desiredLength) + { + this.TestWriteAndReadElements(desiredLength, x => (byte)(x+1), false); + this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), true); + } + + private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue, bool testManagedByteBuffer = false) + where T : struct + { + using (IBuffer buffer = this.Allocate(desiredLength, false, testManagedByteBuffer)) + { + T[] expectedVals = new T[buffer.Length()]; + + for (int i = 0; i < buffer.Length(); i++) + { + Span span = buffer.Span; + expectedVals[i] = getExpectedValue(i); + span[i] = expectedVals[i]; + } + + for (int i = 0; i < buffer.Length(); i++) + { + Span span = buffer.Span; + Assert.Equal(expectedVals[i], span[i]); + } + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength, false); + this.TestIndexOutOfRangeShouldThrow(desiredLength, true); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } + + private T TestIndexOutOfRangeShouldThrow(int desiredLength, bool testManagedByteBuffer = false) + where T : struct, IEquatable + { + var dummy = default(T); + + using (IBuffer buffer = this.Allocate(desiredLength, false, testManagedByteBuffer)) + { + Assert.ThrowsAny( + () => + { + Span span = buffer.Span; + dummy = span[desiredLength]; + }); + + Assert.ThrowsAny( + () => + { + Span span = buffer.Span; + dummy = span[desiredLength + 1]; + }); + + Assert.ThrowsAny( + () => + { + Span 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); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/SimpleManagedMemoryManagerTests.cs b/tests/ImageSharp.Tests/Memory/SimpleManagedMemoryManagerTests.cs new file mode 100644 index 000000000..eb7414582 --- /dev/null +++ b/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()) + { + } + } + } +} \ No newline at end of file