// 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.Runtime.CompilerServices; using CommunityToolkit.HighPerformance.Enumerables; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CommunityToolkit.HighPerformance.UnitTests.Extensions; public partial class Test_ArrayExtensions { [TestMethod] public void Test_ArrayExtensions_2D_DangerousGetReference_Int() { int[,] array = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; // See comments in Test_ArrayExtensions.1D for how these tests work ref int r0 = ref array.DangerousGetReference(); ref int r1 = ref array[0, 0]; Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1)); } [TestMethod] public void Test_ArrayExtensions_2D_DangerousGetReference_String() { string[,] array = { { "a", "bb", "ccc" }, { "dddd", "eeeee", "ffffff" } }; ref string r0 = ref array.DangerousGetReference(); ref string r1 = ref array[0, 0]; Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1)); } [TestMethod] public void Test_ArrayExtensions_2D_DangerousGetReferenceAt_Zero() { int[,] array = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; ref int r0 = ref array.DangerousGetReferenceAt(0, 0); ref int r1 = ref array[0, 0]; Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1)); } [TestMethod] public void Test_ArrayExtensions_2D_DangerousGetReferenceAt_Index() { int[,] array = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; ref int r0 = ref array.DangerousGetReferenceAt(1, 3); ref int r1 = ref array[1, 3]; Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1)); } [TestMethod] public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayMid() { bool[,] test = new bool[4, 5]; // To fill an array we now go through the Span2D type, which includes all // the necessary logic to perform the operation. In these tests we just create // one through the extension, slice it and then fill it. For instance in this // one, we're creating a Span2D from coordinates (1, 1), with a height of // 2 and a width of 2, and then filling it. Then we just compare the results. test.AsSpan2D(1, 1, 2, 3).Fill(true); bool[,]? expected = new[,] { { false, false, false, false, false }, { false, true, true, true, false }, { false, true, true, true, false }, { false, false, false, false, false }, }; CollectionAssert.AreEqual(expected, test); } [TestMethod] public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayTwice() { bool[,] test = new bool[4, 5]; test.AsSpan2D(0, 0, 2, 1).Fill(true); test.AsSpan2D(1, 3, 2, 2).Fill(true); bool[,]? expected = new[,] { { true, false, false, false, false }, { true, false, false, true, true }, { false, false, false, true, true }, { false, false, false, false, false }, }; CollectionAssert.AreEqual(expected, test); } [TestMethod] public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayBottomEdgeBoundary() { bool[,] test = new bool[4, 5]; test.AsSpan2D(1, 2, 3, 2).Fill(true); bool[,]? expected = new[,] { { false, false, false, false, false }, { false, false, true, true, false }, { false, false, true, true, false }, { false, false, true, true, false }, }; CollectionAssert.AreEqual(expected, test); } [TestMethod] public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayBottomRightCornerBoundary() { bool[,] test = new bool[5, 4]; test.AsSpan2D(3, 2, 2, 2).Fill(true); bool[,]? expected = new[,] { { false, false, false, false }, { false, false, false, false }, { false, false, false, false }, { false, false, true, true }, { false, false, true, true }, }; CollectionAssert.AreEqual(expected, test); } [TestMethod] public void Test_ArrayExtensions_2D_GetRow_Rectangle() { int[,] array = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; // Here we use the enumerator on the RefEnumerator type to traverse items in a row // by reference. For each one, we check that the reference does in fact point to the // item we expect in the underlying array (in this case, items on row 1). int j = 0; foreach (ref int value in array.GetRow(1)) { Assert.IsTrue(Unsafe.AreSame(ref value, ref array[1, j++])); } // Check that RefEnumerable.ToArray() works correctly CollectionAssert.AreEqual(array.GetRow(1).ToArray(), new[] { 5, 6, 7, 8 }); // Test an empty array Assert.AreSame(new int[1, 0].GetRow(0).ToArray(), Array.Empty()); _ = Assert.ThrowsException(() => array.GetRow(-1)); _ = Assert.ThrowsException(() => array.GetRow(3)); _ = Assert.ThrowsException(() => array.GetRow(20)); } [TestMethod] public void Test_ArrayExtensions_2D_GetColumn_Rectangle() { int[,] array = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; // Same as above, but this time we iterate a column instead (so non contiguous items) int i = 0; foreach (ref int value in array.GetColumn(1)) { Assert.IsTrue(Unsafe.AreSame(ref value, ref array[i++, 1])); } CollectionAssert.AreEqual(array.GetColumn(1).ToArray(), new[] { 2, 6, 10 }); _ = Assert.ThrowsException(() => array.GetColumn(-1)); _ = Assert.ThrowsException(() => array.GetColumn(4)); _ = Assert.ThrowsException(() => array.GetColumn(20)); } [TestMethod] public void Test_ArrayExtensions_2D_GetRow_Empty() { int[,] array = new int[0, 0]; // Try to get a row from an empty array (the row index isn't in range) _ = Assert.ThrowsException(() => array.GetRow(0).ToArray()); } [TestMethod] public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers() { int[,] array = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 16 } }; // Get a row and test the Clear method. Note that the Span2D here is sliced // starting from the second column, so this method should clear the row from index 1. array.AsSpan2D(1, 1, 3, 3).GetRow(0).Clear(); int[,] expected = { { 1, 2, 3, 4 }, { 5, 0, 0, 0 }, { 9, 10, 11, 12 }, { 13, 14, 15, 16 } }; CollectionAssert.AreEqual(array, expected); // Same as before, but this time we fill a column with a value array.GetColumn(2).Fill(42); expected = new[,] { { 1, 2, 42, 4 }, { 5, 0, 42, 0 }, { 9, 10, 42, 12 }, { 13, 14, 42, 16 } }; CollectionAssert.AreEqual(array, expected); int[] copy = new int[4]; // Get a row and copy items to a target span (in this case, wrapping an array) array.GetRow(2).CopyTo(copy); int[] result = { 9, 10, 42, 12 }; CollectionAssert.AreEqual(copy, result); // Same as above, but copying from a column (so we test non contiguous sequences too) array.GetColumn(1).CopyTo(copy); result = new[] { 2, 0, 10, 14 }; CollectionAssert.AreEqual(copy, result); // Some invalid attempts to copy to an empty span or sequence _ = Assert.ThrowsException(() => array.GetRow(0).CopyTo(default(RefEnumerable))); _ = Assert.ThrowsException(() => array.GetRow(0).CopyTo(default(Span))); _ = Assert.ThrowsException(() => array.GetColumn(0).CopyTo(default(RefEnumerable))); _ = Assert.ThrowsException(() => array.GetColumn(0).CopyTo(default(Span))); // Same as CopyTo, but this will fail gracefully with an invalid target Assert.IsTrue(array.GetRow(2).TryCopyTo(copy)); Assert.IsFalse(array.GetRow(0).TryCopyTo(default(Span))); result = new[] { 9, 10, 42, 12 }; CollectionAssert.AreEqual(copy, result); // Also fill a row and then further down clear a column (trying out all possible combinations) array.GetRow(2).Fill(99); expected = new[,] { { 1, 2, 42, 4 }, { 5, 0, 42, 0 }, { 99, 99, 99, 99 }, { 13, 14, 42, 16 } }; CollectionAssert.AreEqual(array, expected); array.GetColumn(2).Clear(); expected = new[,] { { 1, 2, 0, 4 }, { 5, 0, 0, 0 }, { 99, 99, 0, 99 }, { 13, 14, 0, 16 } }; CollectionAssert.AreEqual(array, expected); } [TestMethod] public void Test_ArrayExtensions_2D_ReadOnlyGetRowOrColumn_Helpers() { int[,] array = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 16 } }; // This test pretty much does the same things as the method above, but this time // using a source ReadOnlySpan2D, so that the sequence type being tested is // ReadOnlyRefEnumerable instead (which shares most features but is separate). ReadOnlySpan2D span2D = array; int[] copy = new int[4]; span2D.GetRow(2).CopyTo(copy); int[] result = { 9, 10, 11, 12 }; CollectionAssert.AreEqual(copy, result); span2D.GetColumn(1).CopyTo(copy); result = new[] { 2, 6, 10, 14 }; CollectionAssert.AreEqual(copy, result); _ = Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetRow(0).CopyTo(default(RefEnumerable))); _ = Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetRow(0).CopyTo(default(Span))); _ = Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetColumn(0).CopyTo(default(RefEnumerable))); _ = Assert.ThrowsException(() => ((ReadOnlySpan2D)array).GetColumn(0).CopyTo(default(Span))); Assert.IsTrue(span2D.GetRow(2).TryCopyTo(copy)); Assert.IsFalse(span2D.GetRow(2).TryCopyTo(default(Span))); result = new[] { 9, 10, 11, 12 }; CollectionAssert.AreEqual(copy, result); } [TestMethod] public void Test_ArrayExtensions_2D_RefEnumerable_Misc() { int[,] array1 = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 16 } }; int[,] array2 = new int[4, 4]; // Copy to enumerable with source step == 1, destination step == 1 array1.GetRow(0).CopyTo(array2.GetRow(0)); // Copy enumerable with source step == 1, destination step != 1 array1.GetRow(1).CopyTo(array2.GetColumn(1)); // Copy enumerable with source step != 1, destination step == 1 array1.GetColumn(2).CopyTo(array2.GetRow(2)); // Copy enumerable with source step != 1, destination step != 1 array1.GetColumn(3).CopyTo(array2.GetColumn(3)); int[,] result = { { 1, 5, 3, 4 }, { 0, 6, 0, 8 }, { 3, 7, 11, 12 }, { 0, 8, 0, 16 } }; CollectionAssert.AreEqual(array2, result); // Test a valid and an invalid TryCopyTo call with the RefEnumerable overload bool shouldBeTrue = array1.GetRow(0).TryCopyTo(array2.GetColumn(0)); bool shouldBeFalse = array1.GetRow(0).TryCopyTo(default(RefEnumerable)); result = new[,] { { 1, 5, 3, 4 }, { 2, 6, 0, 8 }, { 3, 7, 11, 12 }, { 4, 8, 0, 16 } }; CollectionAssert.AreEqual(array2, result); Assert.IsTrue(shouldBeTrue); Assert.IsFalse(shouldBeFalse); } [TestMethod] public void Test_ArrayExtensions_2D_GetColumn_Empty() { int[,] array = new int[0, 0]; _ = Assert.ThrowsException(() => array.GetColumn(0).ToArray()); } #if NET6_0_OR_GREATER [TestMethod] public void Test_ArrayExtensions_2D_AsSpan_Empty() { int[,] array = new int[0, 0]; Span span = array.AsSpan(); // Check that the empty array was loaded properly Assert.AreEqual(span.Length, array.Length); Assert.IsTrue(span.IsEmpty); } [TestMethod] public void Test_ArrayExtensions_2D_AsSpan_Populated() { int[,] array = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; Span span = array.AsSpan(); // Test the total length of the span Assert.AreEqual(span.Length, array.Length); ref int r0 = ref array[0, 0]; ref int r1 = ref span[0]; // Similarly to the top methods, here we compare a given reference to // ensure they point to the right element back in the original array. Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1)); } #endif }