164 lines
6.0 KiB
C#
164 lines
6.0 KiB
C#
namespace QWERTYkez.Mensura.Tests;
|
||
|
||
public class DoubleExtensions
|
||
{
|
||
#region 1. Тесты скалярных типов (Обычные, Экзотические и Nullable)
|
||
|
||
[Fact]
|
||
public void ToDouble_Should_Convert_ExoticTypes_Without_InvalidCastException()
|
||
{
|
||
// Проверяем типы, которые раньше могли падать в рантайме из-за Convert.ToDouble(object)
|
||
Half halfValue = (Half)3.14f;
|
||
Int128 int128Value = Int128.Parse("1234567890123456789012345");
|
||
UInt128 uInt128Value = UInt128.Parse("9876543210987654321098765");
|
||
nint nintValue = 42;
|
||
|
||
// Act & Assert
|
||
Assert.Equal(3.14, halfValue.ToDouble(), 2);
|
||
Assert.Equal((double)int128Value, int128Value.ToDouble());
|
||
Assert.Equal((double)uInt128Value, uInt128Value.ToDouble());
|
||
Assert.Equal(42.0, nintValue.ToDouble());
|
||
}
|
||
|
||
[Theory]
|
||
[InlineData((int)100, 100.0)]
|
||
[InlineData((byte)5, 5.0)]
|
||
[InlineData((float)1.5f, 1.5)]
|
||
public void ToDouble_StandardScalars_ShouldConvertCorrectly(object input, double expected)
|
||
{
|
||
double result = input switch
|
||
{
|
||
int i => i.ToDouble(),
|
||
byte b => b.ToDouble(),
|
||
float f => f.ToDouble(),
|
||
_ => throw new ArgumentException("Unsupported type in test")
|
||
};
|
||
|
||
Assert.Equal(expected, result);
|
||
}
|
||
|
||
[Fact]
|
||
public void ToDouble_NullableScalars_Should_Return_Zero_When_Null()
|
||
{
|
||
// Arrange
|
||
int? nullInt = null;
|
||
Half? nullHalf = null;
|
||
Int128? nullInt128 = null;
|
||
|
||
int? validInt = 10;
|
||
Half? validHalf = (Half)2.5f;
|
||
|
||
// Act & Assert
|
||
Assert.Equal(0d, nullInt.ToDouble());
|
||
Assert.Equal(0d, nullHalf.ToDouble());
|
||
Assert.Equal(0d, nullInt128.ToDouble());
|
||
|
||
Assert.Equal(10d, validInt.ToDouble());
|
||
Assert.Equal(2.5d, validHalf.ToDouble(), 1);
|
||
}
|
||
#endregion
|
||
|
||
#region 2. Тесты SIMD и оптимизаций коллекций (Различные длины массивов)
|
||
|
||
[Theory]
|
||
[InlineData(0)] // Пустой массив
|
||
[InlineData(1)] // 1 элемент (чисто скалярный fallback)
|
||
[InlineData(7)] // Нечетное число элементов
|
||
[InlineData(16)] // Кратный размер (размер Vector128/Vector256)
|
||
[InlineData(35)] // Большой массив с остатком для скалярного хвоста
|
||
public void ToDouble_ArrayAndList_Should_Correctly_Map_Via_SIMD_Or_Fallback(int count)
|
||
{
|
||
// Arrange
|
||
float[] sourceArray = [.. Enumerable.Range(1, count).Select(x => x * 1.5f)];
|
||
List<float> sourceList = [.. sourceArray];
|
||
|
||
double[] destFromArray = new double[count];
|
||
double[] destFromList = new double[count];
|
||
|
||
// Act
|
||
// Явное приведение к IReadOnlyCollection устраняет ошибку CS0121 (неоднозначность вызова)
|
||
((IReadOnlyCollection<float>)sourceArray).ToDouble(destFromArray);
|
||
((IReadOnlyCollection<float>)sourceList).ToDouble(destFromList);
|
||
|
||
// Assert
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
double expected = sourceArray[i];
|
||
Assert.Equal(expected, destFromArray[i]);
|
||
Assert.Equal(expected, destFromList[i]);
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 3. Тесты Nullable-коллекций (Проверка на null-элементы внутри)
|
||
|
||
[Fact]
|
||
public void ToDouble_Nullable_Collections_Should_Keep_Nulls_In_Destination()
|
||
{
|
||
// Arrange
|
||
UInt128?[] sourceArray = [10, null, 20, null, 30];
|
||
double?[] destination = new double?[sourceArray.Length];
|
||
|
||
// Act
|
||
((IReadOnlyCollection<UInt128?>)sourceArray).ToDouble(destination);
|
||
|
||
// Assert
|
||
Assert.Equal(10d, destination[0]);
|
||
Assert.Null(destination[1]);
|
||
Assert.Equal(20d, destination[2]);
|
||
Assert.Null(destination[3]);
|
||
Assert.Equal(30d, destination[4]);
|
||
}
|
||
#endregion
|
||
|
||
#region 4. Тесты LINQ / Отложенного выполнения (IEnumerable)
|
||
|
||
[Fact]
|
||
public void ToDouble_IEnumerable_Extension_Should_Handle_Execution_Types_Correctly()
|
||
{
|
||
// Arrange
|
||
int[] array = [1, 2, 3];
|
||
List<int> list = [4, 5, 6];
|
||
IEnumerable<int> genericEnum = Enumerable.Range(7, 3);
|
||
|
||
// Act
|
||
var resFromArray = array.ToDouble(); // Быстрый бранч для массива
|
||
var resFromList = list.ToDouble(); // Быстрый бранч для списка
|
||
var resFromEnum = genericEnum.ToDouble(); // Медленный итератор по IEnumerable
|
||
|
||
// Assert
|
||
Assert.Equal([1d, 2d, 3d], resFromArray);
|
||
Assert.Equal([4d, 5d, 6d], resFromList);
|
||
Assert.Equal([7d, 8d, 9d], resFromEnum);
|
||
}
|
||
#endregion
|
||
|
||
#region 5. Тесты безопасности и валидации аргументов
|
||
|
||
[Fact]
|
||
public void ToDouble_Should_Throw_ArgumentException_When_Destination_Is_Too_Short()
|
||
{
|
||
// Arrange
|
||
int[] source = [1, 2, 3, 4, 5];
|
||
double[] destinationTooShort = new double[4]; // Нужен размер >= 5
|
||
|
||
// Act & Assert
|
||
Assert.Throws<ArgumentException>(() => ((IReadOnlyCollection<int>)source).ToDouble(destinationTooShort));
|
||
}
|
||
|
||
[Fact]
|
||
public void ToDouble_Should_Do_Nothing_And_Return_When_Collection_Is_Empty_Or_Null()
|
||
{
|
||
// Arrange
|
||
int[]? nullArray = null;
|
||
int[] emptyArray = [];
|
||
double[] destination = new double[5];
|
||
|
||
// Act & Assert (Не должно выбрасывать NullReferenceException или ArgumentException)
|
||
((IReadOnlyCollection<int>?)nullArray)!.ToDouble(destination);
|
||
((IReadOnlyCollection<int>)emptyArray).ToDouble(destination);
|
||
|
||
Assert.All(destination, x => Assert.Equal(0d, x)); // Назначение осталось нетронутым (все нули по умолчанию)
|
||
}
|
||
#endregion
|
||
} |