// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Memory.Allocators;
///
/// Inherit this class to test an implementation (provided by ).
///
public abstract class BufferTestSuite
{
protected BufferTestSuite(MemoryAllocator memoryAllocator)
{
this.MemoryAllocator = memoryAllocator;
}
protected MemoryAllocator MemoryAllocator { 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)
{
return obj is CustomStruct other && this.Equals(other);
}
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 LengthValues = new() { 0, 1, 7, 1023, 1024 };
[Theory]
[MemberData(nameof(LengthValues))]
public void HasCorrectLength_byte(int desiredLength)
{
this.TestHasCorrectLength(desiredLength);
}
[Theory]
[MemberData(nameof(LengthValues))]
public void HasCorrectLength_float(int desiredLength)
{
this.TestHasCorrectLength(desiredLength);
}
[Theory]
[MemberData(nameof(LengthValues))]
public void HasCorrectLength_CustomStruct(int desiredLength)
{
this.TestHasCorrectLength(desiredLength);
}
private void TestHasCorrectLength(int desiredLength)
where T : struct
{
using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength))
{
Assert.Equal(desiredLength, buffer.GetSpan().Length);
}
}
[Theory]
[MemberData(nameof(LengthValues))]
public void CanAllocateCleanBuffer_byte(int desiredLength)
{
this.TestCanAllocateCleanBuffer(desiredLength);
}
[Theory]
[MemberData(nameof(LengthValues))]
public void CanAllocateCleanBuffer_double(int desiredLength)
{
this.TestCanAllocateCleanBuffer(desiredLength);
}
[Theory]
[MemberData(nameof(LengthValues))]
public void CanAllocateCleanBuffer_CustomStruct(int desiredLength)
{
this.TestCanAllocateCleanBuffer(desiredLength);
}
private void TestCanAllocateCleanBuffer(int desiredLength)
where T : struct, IEquatable
{
ReadOnlySpan expected = new T[desiredLength];
for (int i = 0; i < 10; i++)
{
using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength, AllocationOptions.Clean))
{
Assert.True(buffer.GetSpan().SequenceEqual(expected));
}
}
}
[Theory]
[MemberData(nameof(LengthValues))]
public void SpanPropertyIsAlwaysTheSame_int(int desiredLength)
{
this.TestSpanPropertyIsAlwaysTheSame(desiredLength);
}
[Theory]
[MemberData(nameof(LengthValues))]
public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength)
{
this.TestSpanPropertyIsAlwaysTheSame(desiredLength);
}
private void TestSpanPropertyIsAlwaysTheSame(int desiredLength)
where T : struct
{
using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength, AllocationOptions.None))
{
ref T a = ref MemoryMarshal.GetReference(buffer.GetSpan());
ref T b = ref MemoryMarshal.GetReference(buffer.GetSpan());
ref T c = ref MemoryMarshal.GetReference(buffer.GetSpan());
Assert.True(Unsafe.AreSame(ref a, ref b));
Assert.True(Unsafe.AreSame(ref b, ref c));
}
}
[Theory]
[MemberData(nameof(LengthValues))]
public void WriteAndReadElements_float(int desiredLength)
{
this.TestWriteAndReadElements(desiredLength, x => x * 1.2f);
}
[Theory]
[MemberData(nameof(LengthValues))]
public void WriteAndReadElements_byte(int desiredLength)
{
this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1));
}
private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue)
where T : struct
{
using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength))
{
T[] expectedVals = new T[buffer.Length()];
for (int i = 0; i < buffer.Length(); i++)
{
Span span = buffer.GetSpan();
expectedVals[i] = getExpectedValue(i);
span[i] = expectedVals[i];
}
for (int i = 0; i < buffer.Length(); i++)
{
Span span = buffer.GetSpan();
Assert.Equal(expectedVals[i], span[i]);
}
}
}
[Theory]
[MemberData(nameof(LengthValues))]
public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength)
{
this.TestIndexOutOfRangeShouldThrow(desiredLength);
}
[Theory]
[MemberData(nameof(LengthValues))]
public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength)
{
this.TestIndexOutOfRangeShouldThrow(desiredLength);
}
[Theory]
[MemberData(nameof(LengthValues))]
public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength)
{
this.TestIndexOutOfRangeShouldThrow(desiredLength);
}
private T TestIndexOutOfRangeShouldThrow(int desiredLength)
where T : struct, IEquatable
{
T dummy = default(T);
using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength))
{
Assert.ThrowsAny(
() =>
{
Span span = buffer.GetSpan();
dummy = span[desiredLength];
});
Assert.ThrowsAny(
() =>
{
Span span = buffer.GetSpan();
dummy = span[desiredLength + 1];
});
Assert.ThrowsAny(
() =>
{
Span span = buffer.GetSpan();
dummy = span[desiredLength + 42];
});
}
return dummy;
}
[Fact]
public void GetMemory_ReturnsValidMemory()
{
using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(42))
{
Span span0 = buffer.GetSpan();
span0[10].A = 30;
Memory memory = buffer.Memory;
Assert.Equal(42, memory.Length);
Span span1 = memory.Span;
Assert.Equal(42, span1.Length);
Assert.Equal(30, span1[10].A);
}
}
[Fact]
public unsafe void GetMemory_ResultIsPinnable()
{
using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(42))
{
Span span0 = buffer.GetSpan();
span0[10] = 30;
Memory memory = buffer.Memory;
using (MemoryHandle h = memory.Pin())
{
int* ptr = (int*)h.Pointer;
Assert.Equal(30, ptr[10]);
}
}
}
}