// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Linq; using System.Runtime.CompilerServices; using CommunityToolkit.HighPerformance.Enumerables; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CommunityToolkit.HighPerformance.UnitTests; [TestClass] public class Test_Span2DT { [TestMethod] public void Test_Span2DT_Empty() { // Like in the tests for Memory2D, here we validate a number of empty spans Span2D empty1 = default; Assert.IsTrue(empty1.IsEmpty); Assert.AreEqual(empty1.Length, 0); Assert.AreEqual(empty1.Width, 0); Assert.AreEqual(empty1.Height, 0); Span2D empty2 = Span2D.Empty; Assert.IsTrue(empty2.IsEmpty); Assert.AreEqual(empty2.Length, 0); Assert.AreEqual(empty2.Width, 0); Assert.AreEqual(empty2.Height, 0); Span2D empty3 = new int[4, 0]; Assert.IsTrue(empty3.IsEmpty); Assert.AreEqual(empty3.Length, 0); Assert.AreEqual(empty3.Width, 0); Assert.AreEqual(empty3.Height, 4); Span2D empty4 = new int[0, 7]; Assert.IsTrue(empty4.IsEmpty); Assert.AreEqual(empty4.Length, 0); Assert.AreEqual(empty4.Width, 7); Assert.AreEqual(empty4.Height, 0); } #if NET6_0_OR_GREATER [TestMethod] public unsafe void Test_Span2DT_RefConstructor() { Span span = stackalloc[] { 1, 2, 3, 4, 5, 6 }; // Test for a Span2D instance created from a target reference. This is only supported // on runtimes with fast Span support (as we need the API to power this with just a ref). Span2D span2d = Span2D.DangerousCreate(ref span[0], 2, 3, 0); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(span2d.Length, 6); Assert.AreEqual(span2d.Width, 3); Assert.AreEqual(span2d.Height, 2); span2d[0, 0] = 99; span2d[1, 2] = 101; // Validate that those values were mapped to the right spot in the target span Assert.AreEqual(span[0], 99); Assert.AreEqual(span[5], 101); // A few cases with invalid indices _ = Assert.ThrowsException(() => Span2D.DangerousCreate(ref Unsafe.AsRef(null), -1, 0, 0)); _ = Assert.ThrowsException(() => Span2D.DangerousCreate(ref Unsafe.AsRef(null), 1, -2, 0)); _ = Assert.ThrowsException(() => Span2D.DangerousCreate(ref Unsafe.AsRef(null), 1, 0, -5)); } #endif [TestMethod] public unsafe void Test_Span2DT_PtrConstructor() { int* ptr = stackalloc[] { 1, 2, 3, 4, 5, 6 }; // Same as above, but creating a Span2D from a raw pointer Span2D span2d = new(ptr, 2, 3, 0); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(span2d.Length, 6); Assert.AreEqual(span2d.Width, 3); Assert.AreEqual(span2d.Height, 2); span2d[0, 0] = 99; span2d[1, 2] = 101; Assert.AreEqual(ptr[0], 99); Assert.AreEqual(ptr[5], 101); _ = Assert.ThrowsException(() => new Span2D((void*)0, -1, 0, 0)); _ = Assert.ThrowsException(() => new Span2D((void*)0, 1, -2, 0)); _ = Assert.ThrowsException(() => new Span2D((void*)0, 1, 0, -5)); _ = Assert.ThrowsException(() => new Span2D((void*)0, 2, 2, 0)); } [TestMethod] public void Test_Span2DT_Array1DConstructor() { int[] array = { 1, 2, 3, 4, 5, 6 }; // Same as above, but wrapping a 1D array with data in row-major order Span2D span2d = new(array, 1, 2, 2, 1); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(span2d.Length, 4); Assert.AreEqual(span2d.Width, 2); Assert.AreEqual(span2d.Height, 2); span2d[0, 0] = 99; span2d[1, 1] = 101; Assert.AreEqual(array[1], 99); Assert.AreEqual(array[5], 101); // The first check fails due to the array covariance test mentioned in the Memory2D tests. // The others just validate a number of cases with invalid arguments (eg. out of range). _ = Assert.ThrowsException(() => new Span2D(new string[1], 1, 1)); _ = Assert.ThrowsException(() => new Span2D(array, -99, 1, 1, 1)); _ = Assert.ThrowsException(() => new Span2D(array, 0, -10, 1, 1)); _ = Assert.ThrowsException(() => new Span2D(array, 0, 1, 1, -1)); _ = Assert.ThrowsException(() => new Span2D(array, 0, 1, -100, 1)); _ = Assert.ThrowsException(() => new Span2D(array, 0, 10, 1, 120)); } [TestMethod] public void Test_Span2DT_Array2DConstructor_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Same as above, but directly wrapping a 2D array Span2D span2d = new(array); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(span2d.Length, 6); Assert.AreEqual(span2d.Width, 3); Assert.AreEqual(span2d.Height, 2); span2d[0, 1] = 99; span2d[1, 2] = 101; Assert.AreEqual(array[0, 1], 99); Assert.AreEqual(array[1, 2], 101); _ = Assert.ThrowsException(() => new Span2D(new string[1, 2])); } [TestMethod] public void Test_Span2DT_Array2DConstructor_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Same as above, but with a custom slicing over the target 2D array Span2D span2d = new(array, 0, 1, 2, 2); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(span2d.Length, 4); Assert.AreEqual(span2d.Width, 2); Assert.AreEqual(span2d.Height, 2); span2d[0, 0] = 99; span2d[1, 1] = 101; Assert.AreEqual(array[0, 1], 99); Assert.AreEqual(array[1, 2], 101); _ = Assert.ThrowsException(() => new Span2D(new string[1, 2], 0, 0, 2, 2)); } [TestMethod] public void Test_Span2DT_Array3DConstructor_1() { int[,,] array = { { { 1, 2, 3 }, { 4, 5, 6 } }, { { 10, 20, 30 }, { 40, 50, 60 } } }; // Here we wrap a layer in a 3D array instead, the rest is the same Span2D span2d = new(array, 1); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(span2d.Length, 6); Assert.AreEqual(span2d.Width, 3); Assert.AreEqual(span2d.Height, 2); span2d[0, 1] = 99; span2d[1, 2] = 101; Assert.AreEqual(span2d[0, 0], 10); Assert.AreEqual(array[1, 0, 1], 99); Assert.AreEqual(array[1, 1, 2], 101); _ = Assert.ThrowsException(() => new Span2D(array, -1)); _ = Assert.ThrowsException(() => new Span2D(array, 20)); } [TestMethod] public void Test_Span2DT_Array3DConstructor_2() { int[,,] array = { { { 1, 2, 3 }, { 4, 5, 6 } }, { { 10, 20, 30 }, { 40, 50, 60 } } }; // Same as above, but also slicing a target 2D area in the 3D array layer Span2D span2d = new(array, 1, 0, 1, 2, 2); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(span2d.Length, 4); Assert.AreEqual(span2d.Width, 2); Assert.AreEqual(span2d.Height, 2); span2d[0, 1] = 99; span2d[1, 1] = 101; Assert.AreEqual(span2d[0, 0], 20); Assert.AreEqual(array[1, 0, 2], 99); Assert.AreEqual(array[1, 1, 2], 101); _ = Assert.ThrowsException(() => new Span2D(array, -1, 1, 1, 1, 1)); _ = Assert.ThrowsException(() => new Span2D(array, 1, -1, 1, 1, 1)); _ = Assert.ThrowsException(() => new Span2D(array, 1, 1, -1, 1, 1)); _ = Assert.ThrowsException(() => new Span2D(array, 1, 1, 1, -1, 1)); _ = Assert.ThrowsException(() => new Span2D(array, 1, 1, 1, 1, -1)); } [TestMethod] public void Test_Span2DT_FillAndClear_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Tests for the Fill and Clear APIs for Span2D. These should fill // or clear the entire wrapped 2D array (just like eg. Span.Fill). Span2D span2d = new(array); span2d.Fill(42); Assert.IsTrue(array.Cast().All(n => n == 42)); span2d.Clear(); Assert.IsTrue(array.Cast().All(n => n == 0)); } [TestMethod] public void Test_Span2DT_Fill_Empty() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Same as above, but with an initial slicing as well to ensure // these method work correctly with different internal offsets Span2D span2d = new(array, 0, 0, 0, 0); span2d.Fill(42); CollectionAssert.AreEqual(array, array); span2d.Clear(); CollectionAssert.AreEqual(array, array); } [TestMethod] public void Test_Span2DT_FillAndClear_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Same as above, just with different slicing to a target smaller 2D area Span2D span2d = new(array, 0, 1, 2, 2); span2d.Fill(42); int[,] filled = { { 1, 42, 42 }, { 4, 42, 42 } }; CollectionAssert.AreEqual(array, filled); span2d.Clear(); int[,] cleared = { { 1, 0, 0 }, { 4, 0, 0 } }; CollectionAssert.AreEqual(array, cleared); } [TestMethod] public void Test_Span2DT_CopyTo_Empty() { Span2D span2d = Span2D.Empty; int[] target = new int[0]; // Copying an empty Span2D to an empty array is just a no-op span2d.CopyTo(target); } [TestMethod] public void Test_Span2DT_CopyTo_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; Span2D span2d = new(array); int[] target = new int[array.Length]; // Here we copy a Span2D to a target Span mapping an array. // This is valid, and the data will just be copied in row-major order. span2d.CopyTo(target); CollectionAssert.AreEqual(array, target); // Exception due to the target span being too small for the source Span2D instance _ = Assert.ThrowsException(() => new Span2D(array).CopyTo(Span.Empty)); } [TestMethod] public void Test_Span2DT_CopyTo_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Same as above, but with different initial slicing Span2D span2d = new(array, 0, 1, 2, 2); int[] target = new int[4]; span2d.CopyTo(target); int[] expected = { 2, 3, 5, 6 }; CollectionAssert.AreEqual(target, expected); _ = Assert.ThrowsException(() => new Span2D(array).CopyTo(Span.Empty)); _ = Assert.ThrowsException(() => new Span2D(array, 0, 1, 2, 2).CopyTo(Span.Empty)); } [TestMethod] public void Test_Span2DT_CopyTo2D_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; Span2D span2d = new(array); int[,] target = new int[2, 3]; // Same as above, but copying to a target Span2D instead. Note // that this method uses the implicit T[,] to Span2D conversion. span2d.CopyTo(target); CollectionAssert.AreEqual(array, target); _ = Assert.ThrowsException(() => new Span2D(array).CopyTo(Span2D.Empty)); } [TestMethod] public void Test_Span2DT_CopyTo2D_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Same as above, but with extra initial slicing Span2D span2d = new(array, 0, 1, 2, 2); int[,] target = new int[2, 2]; span2d.CopyTo(target); int[,] expected = { { 2, 3 }, { 5, 6 } }; CollectionAssert.AreEqual(target, expected); _ = Assert.ThrowsException(() => new Span2D(array).CopyTo(new Span2D(target))); } [TestMethod] public void Test_Span2DT_TryCopyTo() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; Span2D span2d = new(array); int[] target = new int[array.Length]; // Here we test the safe TryCopyTo method, which will fail gracefully Assert.IsTrue(span2d.TryCopyTo(target)); Assert.IsFalse(span2d.TryCopyTo(Span.Empty)); int[] expected = { 1, 2, 3, 4, 5, 6 }; CollectionAssert.AreEqual(target, expected); } [TestMethod] public void Test_Span2DT_TryCopyTo2D() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Same as above, but copying to a 2D array with the safe TryCopyTo method Span2D span2d = new(array); int[,] target = new int[2, 3]; Assert.IsTrue(span2d.TryCopyTo(target)); Assert.IsFalse(span2d.TryCopyTo(Span2D.Empty)); int[,] expected = { { 1, 2, 3 }, { 4, 5, 6 } }; CollectionAssert.AreEqual(target, expected); } [TestMethod] public unsafe void Test_Span2DT_GetPinnableReference() { // Here we test that a ref from an empty Span2D returns a null ref Assert.IsTrue(Unsafe.AreSame( ref Unsafe.AsRef(null), ref Span2D.Empty.GetPinnableReference())); int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; Span2D span2d = new(array); ref int r0 = ref span2d.GetPinnableReference(); // Here we test that GetPinnableReference returns a ref to the first array element Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); } [TestMethod] public unsafe void Test_Span2DT_DangerousGetReference() { // Same as above, but using DangerousGetReference instead (faster, no conditional check) Assert.IsTrue(Unsafe.AreSame( ref Unsafe.AsRef(null), ref Span2D.Empty.DangerousGetReference())); int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; Span2D span2d = new(array); ref int r0 = ref span2d.DangerousGetReference(); Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); } #if NET6_0_OR_GREATER [TestMethod] public unsafe void Test_Span2DT_Index_Indexer_1() { int[,] array = new int[4, 4]; Span2D span2d = new(array); ref int arrayRef = ref array[1, 3]; ref int span2dRef = ref span2d[1, ^1]; Assert.IsTrue(Unsafe.AreSame(ref arrayRef, ref span2dRef)); } [TestMethod] public unsafe void Test_Span2DT_Index_Indexer_2() { int[,] array = new int[4, 4]; Span2D span2d = new(array); ref int arrayRef = ref array[2, 1]; ref int span2dRef = ref span2d[^2, ^3]; Assert.IsTrue(Unsafe.AreSame(ref arrayRef, ref span2dRef)); } [TestMethod] [ExpectedException(typeof(IndexOutOfRangeException))] public unsafe void Test_Span2DT_Index_Indexer_Fail() { int[,] array = new int[4, 4]; Span2D span2d = new(array); ref int span2dRef = ref span2d[^6, 2]; } [TestMethod] public unsafe void Test_Span2DT_Range_Indexer_1() { int[,] array = new int[4, 4]; Span2D span2d = new(array); Span2D slice = span2d[1.., 1..]; Assert.AreEqual(slice.Length, 9); Assert.IsTrue(Unsafe.AreSame(ref array[1, 1], ref slice[0, 0])); Assert.IsTrue(Unsafe.AreSame(ref array[3, 3], ref slice[2, 2])); } [TestMethod] public unsafe void Test_Span2DT_Range_Indexer_2() { int[,] array = new int[4, 4]; Span2D span2d = new(array); Span2D slice = span2d[0..^2, 1..^1]; Assert.AreEqual(slice.Length, 4); Assert.IsTrue(Unsafe.AreSame(ref array[0, 1], ref slice[0, 0])); Assert.IsTrue(Unsafe.AreSame(ref array[1, 2], ref slice[1, 1])); } [TestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] public unsafe void Test_Span2DT_Range_Indexer_Fail() { int[,] array = new int[4, 4]; Span2D span2d = new(array); _ = span2d[0..6, 2..^1]; Assert.Fail(); } #endif [TestMethod] public void Test_Span2DT_Slice_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Here we have a number of tests that just take an initial 2D array, create a Span2D, // perform a number of slicing operations and then validate the parameters for the resulting // instances, and that the indexer works correctly and maps to the right original elements. Span2D span2d = new(array); Span2D slice1 = span2d.Slice(1, 1, 1, 2); Assert.AreEqual(slice1.Length, 2); Assert.AreEqual(slice1.Height, 1); Assert.AreEqual(slice1.Width, 2); Assert.AreEqual(slice1[0, 0], 5); Assert.AreEqual(slice1[0, 1], 6); Span2D slice2 = span2d.Slice(0, 1, 2, 2); Assert.AreEqual(slice2.Length, 4); Assert.AreEqual(slice2.Height, 2); Assert.AreEqual(slice2.Width, 2); Assert.AreEqual(slice2[0, 0], 2); Assert.AreEqual(slice2[1, 0], 5); Assert.AreEqual(slice2[1, 1], 6); // Some checks for invalid arguments _ = Assert.ThrowsException(() => new Span2D(array).Slice(-1, 1, 1, 1)); _ = Assert.ThrowsException(() => new Span2D(array).Slice(1, -1, 1, 1)); _ = Assert.ThrowsException(() => new Span2D(array).Slice(1, 1, 1, -1)); _ = Assert.ThrowsException(() => new Span2D(array).Slice(1, 1, -1, 1)); _ = Assert.ThrowsException(() => new Span2D(array).Slice(10, 1, 1, 1)); _ = Assert.ThrowsException(() => new Span2D(array).Slice(1, 12, 1, 12)); _ = Assert.ThrowsException(() => new Span2D(array).Slice(1, 1, 55, 1)); } [TestMethod] public void Test_Span2DT_Slice_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; Span2D span2d = new(array); // Same as above, but with some different slicing Span2D slice1 = span2d.Slice(0, 0, 2, 2); Assert.AreEqual(slice1.Length, 4); Assert.AreEqual(slice1.Height, 2); Assert.AreEqual(slice1.Width, 2); Assert.AreEqual(slice1[0, 0], 1); Assert.AreEqual(slice1[1, 1], 5); Span2D slice2 = slice1.Slice(1, 0, 1, 2); Assert.AreEqual(slice2.Length, 2); Assert.AreEqual(slice2.Height, 1); Assert.AreEqual(slice2.Width, 2); Assert.AreEqual(slice2[0, 0], 4); Assert.AreEqual(slice2[0, 1], 5); Span2D slice3 = slice2.Slice(0, 1, 1, 1); Assert.AreEqual(slice3.Length, 1); Assert.AreEqual(slice3.Height, 1); Assert.AreEqual(slice3.Width, 1); Assert.AreEqual(slice3[0, 0], 5); } #if NET6_0_OR_GREATER [TestMethod] public void Test_Span2DT_GetRowSpan() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; Span2D span2d = new(array); // Here we create a Span2D from a 2D array and want to get a Span from // a specific row. This is only supported on runtimes with fast Span support // for the same reason mentioned in the Memory2D tests (we need the Span // constructor that only takes a target ref). Then we just get some references // to items in this span and compare them against references into the original // 2D array to ensure they match and point to the correct elements from there. Span span = span2d.GetRowSpan(1); Assert.IsTrue(Unsafe.AreSame( ref span[0], ref array[1, 0])); Assert.IsTrue(Unsafe.AreSame( ref span[2], ref array[1, 2])); _ = Assert.ThrowsException(() => new Span2D(array).GetRowSpan(-1)); _ = Assert.ThrowsException(() => new Span2D(array).GetRowSpan(5)); } #endif [TestMethod] public void Test_Span2DT_TryGetSpan_From1DArray_1() { int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; Span2D span2d = new(array, 3, 3); bool success = span2d.TryGetSpan(out Span span); Assert.IsTrue(success); Assert.AreEqual(span.Length, span2d.Length); Assert.IsTrue(Unsafe.AreSame(ref array[0], ref span[0])); } [TestMethod] public void Test_Span2DT_TryGetSpan_From1DArray_2() { int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; Span2D span2d = new Span2D(array, 3, 3).Slice(1, 0, 2, 3); bool success = span2d.TryGetSpan(out Span span); Assert.IsTrue(success); Assert.AreEqual(span.Length, span2d.Length); Assert.IsTrue(Unsafe.AreSame(ref array[3], ref span[0])); } [TestMethod] public void Test_Span2DT_TryGetSpan_From1DArray_3() { int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; Span2D span2d = new Span2D(array, 3, 3).Slice(0, 1, 3, 2); bool success = span2d.TryGetSpan(out Span span); Assert.IsFalse(success); Assert.AreEqual(span.Length, 0); } // See https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/3947 [TestMethod] public void Test_Span2DT_TryGetSpan_From1DArray_4() { int[] array = new int[128]; Span2D span2d = new(array, 8, 16); bool success = span2d.TryGetSpan(out Span span); Assert.IsTrue(success); Assert.AreEqual(span.Length, span2d.Length); Assert.IsTrue(Unsafe.AreSame(ref array[0], ref span[0])); } [TestMethod] public void Test_Span2DT_TryGetSpan_From2DArray_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; Span2D span2d = new(array); // This API tries to get a Span for the entire contents of Span2D. // This only works on runtimes if the underlying data is contiguous // and of a size that can fit into a single Span. In this specific test, // this is not expected to work on .NET Standard 2.0 because it can't create a // Span from a 2D array (reasons explained in the comments for the test above). bool success = span2d.TryGetSpan(out Span span); #if NETFRAMEWORK // Can't get a Span over a T[,] array on .NET Standard 2.0 Assert.IsFalse(success); Assert.AreEqual(span.Length, 0); #else Assert.IsTrue(success); Assert.AreEqual(span.Length, span2d.Length); #endif } [TestMethod] public void Test_Span2DT_TryGetSpan_From2DArray_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Same as above, but this will always fail because we're creating // a Span2D wrapping non contiguous data (the pitch is not 0). Span2D span2d = new(array, 0, 0, 2, 2); bool success = span2d.TryGetSpan(out Span span); Assert.IsFalse(success); Assert.IsTrue(span.IsEmpty); } [TestMethod] public void Test_Span2DT_ToArray_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Here we create a Span2D and verify that ToArray() produces // a 2D array that is identical to the original one being wrapped. Span2D span2d = new(array); int[,] copy = span2d.ToArray(); Assert.AreEqual(copy.GetLength(0), array.GetLength(0)); Assert.AreEqual(copy.GetLength(1), array.GetLength(1)); CollectionAssert.AreEqual(array, copy); } [TestMethod] public void Test_Span2DT_ToArray_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Same as above, but with extra initial slicing Span2D span2d = new(array, 0, 0, 2, 2); int[,] copy = span2d.ToArray(); Assert.AreEqual(copy.GetLength(0), 2); Assert.AreEqual(copy.GetLength(1), 2); int[,] expected = { { 1, 2 }, { 4, 5 } }; CollectionAssert.AreEqual(expected, copy); } [TestMethod] [ExpectedException(typeof(NotSupportedException))] public void Test_Span2DT_Equals() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; Span2D span2d = new(array); // Span2D.Equals always throw (this mirrors the behavior of Span.Equals) _ = span2d.Equals(null); } [TestMethod] [ExpectedException(typeof(NotSupportedException))] public void Test_Span2DT_GetHashCode() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; Span2D span2d = new(array); // Same as above, this always throws _ = span2d.GetHashCode(); } [TestMethod] public void Test_Span2DT_ToString() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; Span2D span2d = new(array); // Verify that we get the nicely formatted string string text = span2d.ToString(); const string expected = "CommunityToolkit.HighPerformance.Span2D[2, 3]"; Assert.AreEqual(text, expected); } [TestMethod] public void Test_Span2DT_opEquals() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Create two Span2D instances wrapping the same array with the same // parameters, and verify that the equality operators work correctly. Span2D span2d_1 = new(array); Span2D span2d_2 = new(array); Assert.IsTrue(span2d_1 == span2d_2); Assert.IsFalse(span2d_1 == Span2D.Empty); Assert.IsTrue(Span2D.Empty == Span2D.Empty); // Same as above, but verify that a sliced span is not reported as equal Span2D span2d_3 = new(array, 0, 0, 2, 2); Assert.IsFalse(span2d_1 == span2d_3); Assert.IsFalse(span2d_3 == Span2D.Empty); } [TestMethod] public void Test_Span2DT_ImplicitCast() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Verify that an explicit constructor and the implicit conversion // operator generate an identical Span2D instance from the array. Span2D span2d_1 = array; Span2D span2d_2 = new(array); Assert.IsTrue(span2d_1 == span2d_2); } [TestMethod] public void Test_Span2DT_GetRow() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Get a target row and verify the contents match with our data RefEnumerable enumerable = new Span2D(array).GetRow(1); int[] expected = { 4, 5, 6 }; CollectionAssert.AreEqual(enumerable.ToArray(), expected); _ = Assert.ThrowsException(() => new Span2D(array).GetRow(-1)); _ = Assert.ThrowsException(() => new Span2D(array).GetRow(2)); _ = Assert.ThrowsException(() => new Span2D(array).GetRow(1000)); } [TestMethod] public unsafe void Test_Span2DT_Pointer_GetRow() { int* array = stackalloc[] { 1, 2, 3, 4, 5, 6 }; // Same as above, but with a Span2D wrapping a raw pointer RefEnumerable enumerable = new Span2D(array, 2, 3, 0).GetRow(1); int[] expected = { 4, 5, 6 }; CollectionAssert.AreEqual(enumerable.ToArray(), expected); _ = Assert.ThrowsException(() => new Span2D(array, 2, 3, 0).GetRow(-1)); _ = Assert.ThrowsException(() => new Span2D(array, 2, 3, 0).GetRow(2)); _ = Assert.ThrowsException(() => new Span2D(array, 2, 3, 0).GetRow(1000)); } [TestMethod] public void Test_Span2DT_GetColumn() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; // Same as above, but getting a column instead RefEnumerable enumerable = new Span2D(array).GetColumn(2); int[] expected = { 3, 6 }; CollectionAssert.AreEqual(enumerable.ToArray(), expected); _ = Assert.ThrowsException(() => new Span2D(array).GetColumn(-1)); _ = Assert.ThrowsException(() => new Span2D(array).GetColumn(3)); _ = Assert.ThrowsException(() => new Span2D(array).GetColumn(1000)); } [TestMethod] public unsafe void Test_Span2DT_Pointer_GetColumn() { int* array = stackalloc[] { 1, 2, 3, 4, 5, 6 }; // Same as above, but wrapping a raw pointer RefEnumerable enumerable = new Span2D(array, 2, 3, 0).GetColumn(2); int[] expected = { 3, 6 }; CollectionAssert.AreEqual(enumerable.ToArray(), expected); _ = Assert.ThrowsException(() => new Span2D(array, 2, 3, 0).GetColumn(-1)); _ = Assert.ThrowsException(() => new Span2D(array, 2, 3, 0).GetColumn(3)); _ = Assert.ThrowsException(() => new Span2D(array, 2, 3, 0).GetColumn(1000)); } [TestMethod] public void Test_Span2DT_GetEnumerator() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; int[] result = new int[4]; int i = 0; // Here we want to test the Span2D enumerator. We create a Span2D instance over // a given section of the initial 2D array, then iterate over it and store the items // into a temporary array. We then just compare the contents to ensure they match. foreach (ref int item in new Span2D(array, 0, 1, 2, 2)) { // Check the reference to ensure it points to the right original item Assert.IsTrue(Unsafe.AreSame( ref array[i / 2, (i % 2) + 1], ref item)); // Also store the value to compare it later (redundant, but just in case) result[i++] = item; } int[] expected = { 2, 3, 5, 6 }; CollectionAssert.AreEqual(result, expected); } [TestMethod] public unsafe void Test_Span2DT_Pointer_GetEnumerator() { int* array = stackalloc[] { 1, 2, 3, 4, 5, 6 }; int[] result = new int[4]; int i = 0; // Same test as above, but wrapping a raw pointer foreach (ref int item in new Span2D(array + 1, 2, 2, 1)) { // Check the reference again Assert.IsTrue(Unsafe.AreSame( ref Unsafe.AsRef(&array[((i / 2) * 3) + (i % 2) + 1]), ref item)); result[i++] = item; } int[] expected = { 2, 3, 5, 6 }; CollectionAssert.AreEqual(result, expected); } [TestMethod] public void Test_Span2DT_GetEnumerator_Empty() { Span2D.Enumerator enumerator = Span2D.Empty.GetEnumerator(); // Ensure that an enumerator from an empty Span2D can't move next Assert.IsFalse(enumerator.MoveNext()); } }