This commit is contained in:
melekhin
2026-06-11 15:42:01 +07:00
parent 343996ef46
commit 790a5f8e10
35 changed files with 5128 additions and 3595 deletions

View File

@@ -0,0 +1,170 @@
using System.Reflection;
namespace QWERTYkez.Mensura.Tests;
public class AggregateUnitExtensions
{
// Вспомогательный метод для создания объекта Length.
// Если у вас используется фабричный метод (например, Length.FromMeters), замените код внутри.
private static Length CreateLength(double value) => value * Length._MilliMeter;
#region Инфраструктура Рефлексии (Invoker)
private delegate Length SpanDelegate(ReadOnlySpan<Length> units);
private delegate Length SpanNullableDelegate(ReadOnlySpan<Length?> units);
private static class Invoker
{
private static readonly Type ExtType;
static Invoker()
{
// Находим внутренний класс AggregateUnitExtensions в целевой сборке
ExtType = typeof(Length).Assembly.GetType("QWERTYkez.Mensura.Extensions.AggregateUnitExtensions")
?? throw new InvalidOperationException("Не удалось найти класс AggregateUnitExtensions через рефлексию.");
}
public static Length InvokeSpan(string methodName, ReadOnlySpan<Length> data)
{
var method = FindMethod(methodName, typeof(ReadOnlySpan<>), isNullable: false);
var closedMethod = method.MakeGenericMethod(typeof(Length));
var del = closedMethod.CreateDelegate<SpanDelegate>();
return del(data);
}
public static Length InvokeSpanNullable(string methodName, ReadOnlySpan<Length?> data)
{
var method = FindMethod(methodName, typeof(ReadOnlySpan<>), isNullable: true);
var closedMethod = method.MakeGenericMethod(typeof(Length));
var del = closedMethod.CreateDelegate<SpanNullableDelegate>();
return del(data);
}
public static Length InvokeCollection(string methodName, Type genericContainer, object data, bool isNullable)
{
var method = FindMethod(methodName, genericContainer, isNullable);
var closedMethod = method.MakeGenericMethod(typeof(Length));
return (Length)closedMethod.Invoke(null, [data])!;
}
private static MethodInfo FindMethod(string name, Type genericContainerType, bool isNullable)
{
var methods = ExtType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
.Where(m => m.Name == name);
foreach (var method in methods)
{
var parameters = method.GetParameters();
if (parameters.Length != 1) continue;
var paramType = parameters[0].ParameterType;
if (!paramType.IsGenericType) continue;
// Проверяем базовый контейнер (List<>, IEnumerable<>, ReadOnlySpan<>)
if (paramType.GetGenericTypeDefinition() != genericContainerType) continue;
// Проверяем внутренний аргумент типа на Nullable
var genericArgument = paramType.GetGenericArguments()[0];
bool currentIsNullable = genericArgument.IsGenericType &&
genericArgument.GetGenericTypeDefinition() == typeof(Nullable<>);
if (currentIsNullable == isNullable)
{
return method;
}
}
throw new MethodAccessException($"Метод {name} для контейнера {genericContainerType.Name} (Nullable: {isNullable}) не найден.");
}
}
#endregion
#region Тесты для НЕ-выделяющих Nullable типов (Обычные структуры)
private readonly Length[] _standardData = [CreateLength(10), CreateLength(20), CreateLength(30)];
private readonly Length _expectedSum = CreateLength(60);
private readonly Length _expectedAvg = CreateLength(20);
private readonly Length _expectedMax = CreateLength(30);
private readonly Length _expectedMin = CreateLength(10);
[Theory]
[InlineData("Sum")]
[InlineData("Avg")]
[InlineData("Max")]
[InlineData("Min")]
public void StandardContainers_ShouldCalculateCorrectly(string operation)
{
// Набор ожидаемых значений
Length expected = operation switch
{
"Sum" => _expectedSum,
"Avg" => _expectedAvg,
"Max" => _expectedMax,
"Min" => _expectedMin,
_ => throw new ArgumentException(operation)
};
// 1. Тест ReadOnlySpan<T>
ReadOnlySpan<Length> span = _standardData;
Assert.Equal(expected, Invoker.InvokeSpan(operation, span));
// 2. Тест List<T>
var list = _standardData.ToList();
Assert.Equal(expected, Invoker.InvokeCollection(operation, typeof(List<>), list, isNullable: false));
// 3. Тест IReadOnlyCollection<T>
IReadOnlyCollection<Length> readOnlyCollection = _standardData;
Assert.Equal(expected, Invoker.InvokeCollection(operation, typeof(IReadOnlyCollection<>), readOnlyCollection, isNullable: false));
// 4. Тест IEnumerable<T>
IEnumerable<Length> enumerable = _standardData.Select(x => x);
Assert.Equal(expected, Invoker.InvokeCollection(operation, typeof(IEnumerable<>), enumerable, isNullable: false));
}
#endregion
#region Тесты для Nullable типов (T?)
// Тестируем смесь значений с null-элементами
private readonly Length?[] _nullableData = [CreateLength(10), null, CreateLength(30)];
private readonly Length _expectedNullSum = CreateLength(40);
private readonly Length _expectedNullAvg = CreateLength(20); // 40 / 2 значения
private readonly Length _expectedNullMax = CreateLength(30);
private readonly Length _expectedNullMin = CreateLength(10);
[Theory]
[InlineData("Sum")]
[InlineData("Avg")]
[InlineData("Max")]
[InlineData("Min")]
public void NullableContainers_ShouldCalculateCorrectly(string operation)
{
Length expected = operation switch
{
"Sum" => _expectedNullSum,
"Avg" => _expectedNullAvg,
"Max" => _expectedNullMax,
"Min" => _expectedNullMin,
_ => throw new ArgumentException(operation)
};
// 1. Тест ReadOnlySpan<T?>
ReadOnlySpan<Length?> span = _nullableData;
Assert.Equal(expected, Invoker.InvokeSpanNullable(operation, span));
// 2. Тест List<T?>
var list = _nullableData.ToList();
Assert.Equal(expected, Invoker.InvokeCollection(operation, typeof(List<>), list, isNullable: true));
// 3. Тест IReadOnlyCollection<T?>
IReadOnlyCollection<Length?> readOnlyCollection = _nullableData;
Assert.Equal(expected, Invoker.InvokeCollection(operation, typeof(IReadOnlyCollection<>), readOnlyCollection, isNullable: true));
// 4. Тест IEnumerable<T?>
IEnumerable<Length?> enumerable = _nullableData.Select(x => x);
Assert.Equal(expected, Invoker.InvokeCollection(operation, typeof(IEnumerable<>), enumerable, isNullable: true));
}
#endregion
}

View File

@@ -0,0 +1,165 @@
namespace QWERTYkez.Mensura.Tests;
public class CastExtensions
{
private const double NormalValue1 = 42.42;
private const double NormalValue2 = 100.05;
#region 1. Слабое место: Побитовая идентичность и спец-значения (NaN/Inf)
// Так как используется Unsafe.As, нужно убедиться, что битовая сетка double
// не ломается при сохранении пограничных значений (NaN, Infinity).
[Theory]
[InlineData(NormalValue1)]
[InlineData(double.NaN)]
[InlineData(double.PositiveInfinity)]
[InlineData(double.NegativeInfinity)]
[InlineData(double.Epsilon)]
public void SingleConversion_Should_Preserve_Exact_BitPattern_For_Special_Doubles(double specialValue)
{
// Act
Length unit = specialValue.ToUnit<Length>();
double recovered = unit.ToDouble();
// Assert
// Используем BitConverter, чтобы проверить идентичность на уровне битов (особенно важно для NaN)
long originalBits = BitConverter.DoubleToInt64Bits(specialValue);
long recoveredBits = BitConverter.DoubleToInt64Bits(recovered);
Assert.Equal(originalBits, recoveredBits);
}
#endregion
#region 2. Слабое место: Взрыв CLR при изменении емкости (EnsureCapacity)
// При ReCast списков подменяется MethodTable самого списка и его внутреннего массива.
// Если мы начнем добавлять элементы, список выделит новый массив через Array.CreateInstance.
// Если тип в MethodTable не совпадет с тем, что ожидает среда исполнения, GC или рантайм упадет.
[Fact]
public void List_ReCast_Should_Survive_Massive_Resize_And_GC_Collections()
{
// Arrange
var originalList = new List<Length> { (Length)NormalValue1, (Length)NormalValue2 };
// Act
List<double> morphedList = originalList.ReCast();
// Провоцируем многократное выделение новой памяти (Resize внутреннего массива)
for (int i = 0; i < 100; i++)
{
morphedList.Add(i * 1.1);
}
// Вызываем сборщик мусора, чтобы проверить, не сошел ли он с ума от нашей подмены
GC.Collect();
GC.WaitForPendingFinalizers();
// Assert
Assert.Equal(102, morphedList.Count);
Assert.Equal(NormalValue1, morphedList[0]);
Assert.Equal(10 * 1.1, morphedList[12], 5); // Проверка случайного элемента
}
#endregion
#region 3. Слабое место: Ловушка `ArrayTypeMismatchException` при добавлении в WrapAsList
// WrapAsList создает фейковый список, подменяя MethodTable исходного массива на double[].
// Но когда мы вызываем List.Add(), рантайм внутри делает Array.Copy().
// Если CLR поймет, что исходный массив физически был массивом структур Length,
// вылетит ArrayTypeMismatchException.
[Fact]
public void WrapAsList_Must_Allow_Adding_Elements_Without_ArrayTypeMismatchException()
{
// Arrange
Length[] sourceArray = [(Length)NormalValue1, (Length)NormalValue2];
// Act
List<double> wrappedList = sourceArray.WrapAsList();
// Assert
// Это действие вызывает внутренний Array.Copy. Самое хрупкое место!
var exception = Record.Exception(() => wrappedList.Add(999.99));
Assert.Null(exception); // Тест провален, если здесь вылетит исключение типа массива
Assert.Equal(3, wrappedList.Count);
Assert.Equal(999.99, wrappedList[2]);
}
#endregion
#region 4. Слабое место: Выравнивание памяти и макет Nullable типов
// Структура Nullable<T> в памяти имеет размер больше, чем T (из-за флага HasValue и выравнивания).
// Подмена MethodTable для Nullable<Length>[] в Nullable<double>[] — это огромный риск
// смещения байт. Проверяем, что null остается null, а значения не затираются.
[Fact]
public void Nullable_Array_ReCast_Should_Not_Corrupt_Flags_And_Values()
{
// Arrange
Length?[] source = [(Length)NormalValue1, null, (Length)NormalValue2, null];
// Act
double?[] morphed = source.ReCast();
// Assert
Assert.Equal(source.Length, morphed.Length);
Assert.Equal(NormalValue1, morphed[0]);
Assert.Null(morphed[1]);
Assert.Equal(NormalValue2, morphed[2]);
Assert.Null(morphed[3]);
}
#endregion
#region 5. Слабое место: In-Place мутация (Побочный эффект "Вуду")
// Так как ReCast и WrapAsList меняют MethodTable *оригинального* объекта прямо в куче,
// старая ссылка на массив Length[] теперь указывает на объект, который думает, что он double[].
// Проверяем, как ведет себя оригинальная переменная после этого хака.
[Fact]
public void Verify_InPlace_Mutation_SideEffect_Does_Not_Crash_Old_Reference()
{
// Arrange
Length[] originalArray = [(Length)NormalValue1];
// Act
double[] morphedArray = originalArray.ReCast();
// Изменяем элемент через НОВЫЙ массив
morphedArray[0] = 777.77;
// Assert
// Внимание: из-за жесткой мутации в куче originalArray[0] теперь ТОЖЕ вернет 777.77,
// потому что они смотрят на одну память. Главное — чтобы CLR не упал при обращении к старой ссылке.
double valueFromOldRef = (double)originalArray[0];
Assert.Equal(777.77, valueFromOldRef);
Assert.Same(originalArray, morphedArray); // Физически это один и тот же объект в памяти
}
#endregion
#region 6. Слабое место: Фильтры CLR и оптимизации LINQ
// Компилятор и JIT часто оптимизируют методы вроде .Select() или .ToArray(),
// опираясь на тип MethodTable. Проверяем, съедят ли механизмы LINQ наш "поддельный" тип.
[Fact]
public void Morphed_Array_Should_Pass_Through_Linq_And_Native_Sorting_Validations()
{
// Arrange
Length[] originalArray = [(Length)NormalValue2, (Length)NormalValue1]; // [100.05, 42.42]
// Act
double[] morphedArray = originalArray.ReCast();
// 1. Проверка LINQ фильтрации
double[] processedViaLinq = [.. morphedArray.Where(x => x > 50.0)];
Assert.Single(processedViaLinq);
Assert.Equal(NormalValue2, processedViaLinq[0]);
// 2. Проверка работы встроенной нативной сортировки (Array.Sort использует JIT-оптимизации)
var sortException = Record.Exception(() => Array.Sort(morphedArray));
Assert.Null(sortException);
Assert.True(morphedArray[0] < morphedArray[1]); // Теперь [42.42, 100.05]
}
#endregion
}

View File

@@ -0,0 +1,292 @@
namespace QWERTYkez.Mensura.Tests
{
public class CollectionsDivideExtensionsTests
{
private const double Tolerance = 1e-12;
private static readonly Length scalarUnit = Length.Meter; // 1000 mm
private static readonly double scalarDouble = 2.0;
// Коллекции для тестирования: два элемента
private static readonly Length[] unitsArray = [Length.Meter, Length._MilliMeter];
private static readonly Length?[] nullableUnitsArray = [Length.Meter, null, Length._MilliMeter];
private static readonly List<Length> unitsList = [Length.Meter, Length._MilliMeter];
private static readonly List<Length?> nullableUnitsList = [Length.Meter, null, Length._MilliMeter];
private static readonly double[] doubleArray = [2.0, 3.0];
private static readonly double?[] nullableDoubleArray = [2.0, null, 3.0];
// ====================== 1. T[] / double ======================
[Fact]
public void Div_TArray_Double_Returns_TArray()
{
var result = unitsArray.Div<Length>(scalarDouble);
Assert.Equal(2, result.Length);
Assert.Equal(1000 / 2.0, (double)result[0], Tolerance);
Assert.Equal(1 / 2.0, (double)result[1], Tolerance);
}
// ====================== 2. T?[] / double ======================
[Fact]
public void Div_NullableTArray_Double_Returns_NullableTArray()
{
var result = nullableUnitsArray.Div<Length>(scalarDouble);
Assert.Equal(3, result.Length);
Assert.Equal(1000 / 2.0, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1 / 2.0, (double)result[2]!, Tolerance);
}
// ====================== 3. double / T[] ======================
[Fact]
public void Div_Double_TArray_Returns_TArray()
{
var result = scalarDouble.Div<Length>(unitsArray);
Assert.Equal(2, result.Length);
Assert.Equal(2.0 / 1000, (double)result[0], Tolerance);
Assert.Equal(2.0 / 1, (double)result[1], Tolerance);
}
// ====================== 4. double / T?[] ======================
[Fact]
public void Div_Double_NullableTArray_Returns_NullableTArray()
{
var result = scalarDouble.Div<Length>(nullableUnitsArray);
Assert.Equal(3, result.Length);
Assert.Equal(2.0 / 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(2.0 / 1, (double)result[2]!, Tolerance);
}
// ====================== 5. List<T> / double ======================
[Fact]
public void Div_ListT_Double_Returns_ListT()
{
var result = unitsList.Div<Length>(scalarDouble);
Assert.Equal(2, result.Count);
Assert.Equal(1000 / 2.0, (double)result[0], Tolerance);
Assert.Equal(1 / 2.0, (double)result[1], Tolerance);
}
// ====================== 6. List<T?> / double ======================
[Fact]
public void Div_ListNullableT_Double_Returns_ListNullableT()
{
var result = nullableUnitsList.Div<Length>(scalarDouble);
Assert.Equal(3, result.Count);
Assert.Equal(1000 / 2.0, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1 / 2.0, (double)result[2]!, Tolerance);
}
// ====================== 7. double / List<T> ======================
[Fact]
public void Div_Double_ListT_Returns_ListT()
{
var result = scalarDouble.Div<Length>(unitsList);
Assert.Equal(2, result.Count);
Assert.Equal(2.0 / 1000, (double)result[0], Tolerance);
Assert.Equal(2.0 / 1, (double)result[1], Tolerance);
}
// ====================== 8. double / List<T?> ======================
[Fact]
public void Div_Double_ListNullableT_Returns_ListNullableT()
{
var result = scalarDouble.Div<Length>(nullableUnitsList);
Assert.Equal(3, result.Count);
Assert.Equal(2.0 / 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(2.0 / 1, (double)result[2]!, Tolerance);
}
// ====================== 9. IReadOnlyCollection<T> / double (Span) ======================
[Fact]
public void Div_IReadOnlyCollectionT_Double_Span()
{
Span<Length> dest = new Length[2];
((IReadOnlyCollection<Length>)unitsArray).Div(scalarDouble, dest);
Assert.Equal(1000 / 2.0, (double)dest[0], Tolerance);
Assert.Equal(1 / 2.0, (double)dest[1], Tolerance);
}
// ====================== 10. IReadOnlyCollection<T?> / double (Span) ======================
[Fact]
public void Div_IReadOnlyCollectionNullableT_Double_Span()
{
Span<Length?> dest = new Length?[3];
((IReadOnlyCollection<Length?>)nullableUnitsArray).Div(scalarDouble, dest);
Assert.Equal(1000 / 2.0, (double)dest[0]!, Tolerance);
Assert.Null(dest[1]);
Assert.Equal(1 / 2.0, (double)dest[2]!, Tolerance);
}
// ====================== 11. double / IReadOnlyCollection<T> (Span) ======================
[Fact]
public void Div_Double_IReadOnlyCollectionT_Span()
{
Span<Length> dest = new Length[2];
scalarDouble.Div((IReadOnlyCollection<Length>)unitsArray, dest);
Assert.Equal(2.0 / 1000, (double)dest[0], Tolerance);
Assert.Equal(2.0 / 1, (double)dest[1], Tolerance);
}
// ====================== 12. double / IReadOnlyCollection<T?> (Span) ======================
[Fact]
public void Div_Double_IReadOnlyCollectionNullableT_Span()
{
Span<Length?> dest = new Length?[3];
scalarDouble.Div((IReadOnlyCollection<Length?>)nullableUnitsArray, dest);
Assert.Equal(2.0 / 1000, (double)dest[0]!, Tolerance);
Assert.Null(dest[1]);
Assert.Equal(2.0 / 1, (double)dest[2]!, Tolerance);
}
// ====================== 13. IEnumerable<T> / double ======================
[Fact]
public void Div_IEnumerableT_Double_Returns_IEnumerableT()
{
var result = ((IEnumerable<Length>)unitsArray).Div<Length>(scalarDouble).ToList();
Assert.Equal(2, result.Count);
Assert.Equal(1000 / 2.0, (double)result[0], Tolerance);
Assert.Equal(1 / 2.0, (double)result[1], Tolerance);
}
// ====================== 14. IEnumerable<T?> / double ======================
[Fact]
public void Div_IEnumerableNullableT_Double_Returns_IEnumerableNullableT()
{
var result = ((IEnumerable<Length?>)nullableUnitsArray).Div<Length>(scalarDouble).ToList();
Assert.Equal(3, result.Count);
Assert.Equal(1000 / 2.0, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1 / 2.0, (double)result[2]!, Tolerance);
}
// ====================== 15. double / IEnumerable<T> ======================
[Fact]
public void Div_Double_IEnumerableT_Returns_IEnumerableT()
{
var result = scalarDouble.Div<Length>((IEnumerable<Length>)unitsArray).ToList();
Assert.Equal(2, result.Count);
Assert.Equal(2.0 / 1000, (double)result[0], Tolerance);
Assert.Equal(2.0 / 1, (double)result[1], Tolerance);
}
// ====================== 16. double / IEnumerable<T?> ======================
[Fact]
public void Div_Double_IEnumerableNullableT_Returns_IEnumerableNullableT()
{
var result = scalarDouble.Div<Length>((IEnumerable<Length?>)nullableUnitsArray).ToList();
Assert.Equal(3, result.Count);
Assert.Equal(2.0 / 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(2.0 / 1, (double)result[2]!, Tolerance);
}
// ====================== 17. double[] / T ======================
[Fact]
public void Div_DoubleArray_T_Returns_TArray()
{
var result = doubleArray.Div(scalarUnit);
Assert.Equal(2, result.Length);
Assert.Equal(2.0 / 1000, (double)result[0], Tolerance);
Assert.Equal(3.0 / 1000, (double)result[1], Tolerance);
}
// ====================== 18. double?[] / T ======================
[Fact]
public void Div_NullableDoubleArray_T_Returns_NullableTArray()
{
var result = nullableDoubleArray.Div(scalarUnit);
Assert.Equal(3, result.Length);
Assert.Equal(2.0 / 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(3.0 / 1000, (double)result[2]!, Tolerance);
}
// ====================== 19. T / double[] ======================
[Fact]
public void Div_T_DoubleArray_Returns_TArray()
{
var result = scalarUnit.Div(doubleArray);
Assert.Equal(2, result.Length);
Assert.Equal(1000 / 2.0, (double)result[0], Tolerance);
Assert.Equal(1000 / 3.0, (double)result[1], Tolerance);
}
// ====================== 20. T / double?[] ======================
[Fact]
public void Div_T_NullableDoubleArray_Returns_NullableTArray()
{
var result = scalarUnit.Div(nullableDoubleArray);
Assert.Equal(3, result.Length);
Assert.Equal(1000 / 2.0, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1000 / 3.0, (double)result[2]!, Tolerance);
}
// ====================== 21. List<double> / T ======================
[Fact]
public void Div_ListDouble_T_Returns_ListT()
{
var list = new List<double> { 2.0, 3.0 };
var result = list.Div(scalarUnit);
Assert.Equal(2, result.Count);
Assert.Equal(2.0 / 1000, (double)result[0], Tolerance);
Assert.Equal(3.0 / 1000, (double)result[1], Tolerance);
}
// ====================== 22. List<double?> / T ======================
[Fact]
public void Div_ListNullableDouble_T_Returns_ListNullableT()
{
var list = new List<double?> { 2.0, null, 3.0 };
var result = list.Div(scalarUnit);
Assert.Equal(3, result.Count);
Assert.Equal(2.0 / 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(3.0 / 1000, (double)result[2]!, Tolerance);
}
// ====================== 23. T / List<double> ======================
[Fact]
public void Div_T_ListDouble_Returns_ListT()
{
var list = new List<double> { 2.0, 3.0 };
var result = scalarUnit.Div(list);
Assert.Equal(2, result.Count);
Assert.Equal(1000 / 2.0, (double)result[0], Tolerance);
Assert.Equal(1000 / 3.0, (double)result[1], Tolerance);
}
// ====================== 24. T / List<double?> ======================
[Fact]
public void Div_T_ListNullableDouble_Returns_ListNullableT()
{
var list = new List<double?> { 2.0, null, 3.0 };
var result = scalarUnit.Div(list);
Assert.Equal(3, result.Count);
Assert.Equal(1000 / 2.0, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1000 / 3.0, (double)result[2]!, Tolerance);
}
// ====================== 25. T / T[] -> double[] ======================
[Fact]
public void Div_T_TArray_Returns_DoubleArray()
{
var result = scalarUnit.Div(unitsArray);
Assert.Equal(2, result.Length);
Assert.Equal(1000 / 1000.0, result[0], Tolerance);
Assert.Equal(1000 / 1.0, result[1], Tolerance);
}
// ====================== Дополнительно: пустые коллекции, null аргументы ======================
[Fact]
public void Div_EmptyArray_ReturnsEmptyArray()
{
var empty = Array.Empty<Length>();
var result = empty.Div<Length>(2.0);
Assert.Empty(result);
}
}
}

View File

@@ -0,0 +1,337 @@
namespace QWERTYkez.Mensura.Tests
{
public class CollectionsMinusExtensionsTests
{
private const double Tolerance = 1e-12;
private static readonly Length scalarUnit = Length.Meter; // 1000 mm
private static readonly double scalarDouble = 1000.0; // уменьшаемое/вычитаемое в мм
// Коллекции единиц
private static readonly Length[] unitsArray = [Length.Meter, Length._MilliMeter];
private static readonly Length?[] nullableUnitsArray = [Length.Meter, null, Length._MilliMeter];
private static readonly List<Length> unitsList = [Length.Meter, Length._MilliMeter];
private static readonly List<Length?> nullableUnitsList = [Length.Meter, null, Length._MilliMeter];
// Коллекции double
private static readonly double[] doubleArray = [500.0, 200.0]; // мм
private static readonly double?[] nullableDoubleArray = [500.0, null, 200.0];
private static readonly List<double> doubleList = [500.0, 200.0];
private static readonly List<double?> nullableDoubleList = [500.0, null, 200.0];
// ====================== 1. T[] - double (результат T[]) ======================
[Fact]
public void Minus_TArray_Double_Returns_TArray()
{
// units - subtrahend
var result = unitsArray.Minus<Length>(scalarDouble);
Assert.Equal(2, result.Length);
Assert.Equal(1000 - 1000, (double)result[0], Tolerance);
Assert.Equal(1 - 1000, (double)result[1], Tolerance);
}
[Fact]
public void Minus_TArray_Double_ByZero_Works()
{
var result = unitsArray.Minus<Length>(0.0);
Assert.Equal(1000, (double)result[0], Tolerance);
Assert.Equal(1, (double)result[1], Tolerance);
}
// ====================== 2. T?[] - double (результат T?[]) ======================
[Fact]
public void Minus_NullableTArray_Double_Returns_NullableTArray()
{
var result = nullableUnitsArray.Minus<Length>(scalarDouble);
Assert.Equal(3, result.Length);
Assert.Equal(1000 - 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1 - 1000, (double)result[2]!, Tolerance);
}
// ====================== 3. double - T[] (результат T[]) ======================
[Fact]
public void Minus_Double_TArray_Returns_TArray()
{
var result = scalarDouble.Minus<Length>(unitsArray);
Assert.Equal(2, result.Length);
Assert.Equal(1000 - 1000, (double)result[0], Tolerance);
Assert.Equal(1000 - 1, (double)result[1], Tolerance);
}
// ====================== 4. double - T?[] (результат T?[]) ======================
[Fact]
public void Minus_Double_NullableTArray_Returns_NullableTArray()
{
var result = scalarDouble.Minus<Length>(nullableUnitsArray);
Assert.Equal(3, result.Length);
Assert.Equal(1000 - 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1000 - 1, (double)result[2]!, Tolerance);
}
// ====================== 5. List<T> - double ======================
[Fact]
public void Minus_ListT_Double_Returns_ListT()
{
var result = unitsList.Minus<Length>(scalarDouble);
Assert.Equal(2, result.Count);
Assert.Equal(1000 - 1000, (double)result[0], Tolerance);
Assert.Equal(1 - 1000, (double)result[1], Tolerance);
}
// ====================== 6. List<T?> - double ======================
[Fact]
public void Minus_ListNullableT_Double_Returns_ListNullableT()
{
var result = nullableUnitsList.Minus<Length>(scalarDouble);
Assert.Equal(3, result.Count);
Assert.Equal(1000 - 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1 - 1000, (double)result[2]!, Tolerance);
}
// ====================== 7. double - List<T> ======================
[Fact]
public void Minus_Double_ListT_Returns_ListT()
{
var result = scalarDouble.Minus<Length>(unitsList);
Assert.Equal(2, result.Count);
Assert.Equal(1000 - 1000, (double)result[0], Tolerance);
Assert.Equal(1000 - 1, (double)result[1], Tolerance);
}
// ====================== 8. double - List<T?> ======================
[Fact]
public void Minus_Double_ListNullableT_Returns_ListNullableT()
{
var result = scalarDouble.Minus<Length>(nullableUnitsList);
Assert.Equal(3, result.Count);
Assert.Equal(1000 - 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1000 - 1, (double)result[2]!, Tolerance);
}
// ====================== 9. IReadOnlyCollection<T> - double (Span) ======================
[Fact]
public void Minus_IReadOnlyCollectionT_Double_Span()
{
Span<Length> dest = new Length[2];
((IReadOnlyCollection<Length>)unitsArray).Minus<Length>(scalarDouble, dest);
Assert.Equal(1000 - 1000, (double)dest[0], Tolerance);
Assert.Equal(1 - 1000, (double)dest[1], Tolerance);
}
// ====================== 10. IReadOnlyCollection<T?> - double (Span) ======================
[Fact]
public void Minus_IReadOnlyCollectionNullableT_Double_Span()
{
Span<Length?> dest = new Length?[3];
((IReadOnlyCollection<Length?>)nullableUnitsArray).Minus<Length>(scalarDouble, dest);
Assert.Equal(1000 - 1000, (double)dest[0]!, Tolerance);
Assert.Null(dest[1]);
Assert.Equal(1 - 1000, (double)dest[2]!, Tolerance);
}
// ====================== 11. double - IReadOnlyCollection<T> (Span) ======================
[Fact]
public void Minus_Double_IReadOnlyCollectionT_Span()
{
Span<Length> dest = new Length[2];
scalarDouble.Minus<Length>((IReadOnlyCollection<Length>)unitsArray, dest);
Assert.Equal(1000 - 1000, (double)dest[0], Tolerance);
Assert.Equal(1000 - 1, (double)dest[1], Tolerance);
}
// ====================== 12. double - IReadOnlyCollection<T?> (Span) ======================
[Fact]
public void Minus_Double_IReadOnlyCollectionNullableT_Span()
{
Span<Length?> dest = new Length?[3];
scalarDouble.Minus<Length>((IReadOnlyCollection<Length?>)nullableUnitsArray, dest);
Assert.Equal(1000 - 1000, (double)dest[0]!, Tolerance);
Assert.Null(dest[1]);
Assert.Equal(1000 - 1, (double)dest[2]!, Tolerance);
}
// ====================== 13. IEnumerable<T> - double ======================
[Fact]
public void Minus_IEnumerableT_Double_Returns_IEnumerableT()
{
var result = ((IEnumerable<Length>)unitsArray).Minus<Length>(scalarDouble).ToList();
Assert.Equal(2, result.Count);
Assert.Equal(1000 - 1000, (double)result[0], Tolerance);
Assert.Equal(1 - 1000, (double)result[1], Tolerance);
}
// ====================== 14. IEnumerable<T?> - double ======================
[Fact]
public void Minus_IEnumerableNullableT_Double_Returns_IEnumerableNullableT()
{
var result = ((IEnumerable<Length?>)nullableUnitsArray).Minus<Length>(scalarDouble).ToList();
Assert.Equal(3, result.Count);
Assert.Equal(1000 - 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1 - 1000, (double)result[2]!, Tolerance);
}
// ====================== 15. double - IEnumerable<T> ======================
[Fact]
public void Minus_Double_IEnumerableT_Returns_IEnumerableT()
{
var result = scalarDouble.Minus<Length>((IEnumerable<Length>)unitsArray).ToList();
Assert.Equal(2, result.Count);
Assert.Equal(1000 - 1000, (double)result[0], Tolerance);
Assert.Equal(1000 - 1, (double)result[1], Tolerance);
}
// ====================== 16. double - IEnumerable<T?> ======================
[Fact]
public void Minus_Double_IEnumerableNullableT_Returns_IEnumerableNullableT()
{
var result = scalarDouble.Minus<Length>((IEnumerable<Length?>)nullableUnitsArray).ToList();
Assert.Equal(3, result.Count);
Assert.Equal(1000 - 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1000 - 1, (double)result[2]!, Tolerance);
}
// ====================== 17. double[] - T ======================
[Fact]
public void Minus_DoubleArray_T_Returns_TArray()
{
var result = doubleArray.Minus(scalarUnit);
Assert.Equal(2, result.Length);
Assert.Equal(500 - 1000, (double)result[0], Tolerance);
Assert.Equal(200 - 1000, (double)result[1], Tolerance);
}
// ====================== 18. double?[] - T ======================
[Fact]
public void Minus_NullableDoubleArray_T_Returns_NullableTArray()
{
var result = nullableDoubleArray.Minus(scalarUnit);
Assert.Equal(3, result.Length);
Assert.Equal(500 - 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(200 - 1000, (double)result[2]!, Tolerance);
}
// ====================== 19. T - double[] ======================
[Fact]
public void Minus_T_DoubleArray_Returns_TArray()
{
var result = scalarUnit.Minus(doubleArray);
Assert.Equal(2, result.Length);
Assert.Equal(1000 - 500, (double)result[0], Tolerance);
Assert.Equal(1000 - 200, (double)result[1], Tolerance);
}
// ====================== 20. T - double?[] ======================
[Fact]
public void Minus_T_NullableDoubleArray_Returns_NullableTArray()
{
var result = scalarUnit.Minus(nullableDoubleArray);
Assert.Equal(3, result.Length);
Assert.Equal(1000 - 500, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1000 - 200, (double)result[2]!, Tolerance);
}
// ====================== 21. List<double> - T ======================
[Fact]
public void Minus_ListDouble_T_Returns_ListT()
{
var result = doubleList.Minus(scalarUnit);
Assert.Equal(2, result.Count);
Assert.Equal(500 - 1000, (double)result[0], Tolerance);
Assert.Equal(200 - 1000, (double)result[1], Tolerance);
}
// ====================== 22. List<double?> - T ======================
[Fact]
public void Minus_ListNullableDouble_T_Returns_ListNullableT()
{
var result = nullableDoubleList.Minus(scalarUnit);
Assert.Equal(3, result.Count);
Assert.Equal(500 - 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(200 - 1000, (double)result[2]!, Tolerance);
}
// ====================== 23. T - List<double> ======================
[Fact]
public void Minus_T_ListDouble_Returns_ListT()
{
var result = scalarUnit.Minus(doubleList);
Assert.Equal(2, result.Count);
Assert.Equal(1000 - 500, (double)result[0], Tolerance);
Assert.Equal(1000 - 200, (double)result[1], Tolerance);
}
// ====================== 24. T - List<double?> ======================
[Fact]
public void Minus_T_ListNullableDouble_Returns_ListNullableT()
{
var result = scalarUnit.Minus(nullableDoubleList);
Assert.Equal(3, result.Count);
Assert.Equal(1000 - 500, (double)result[0]!, Tolerance);
Assert.Null(result[1]);
Assert.Equal(1000 - 200, (double)result[2]!, Tolerance);
}
// ====================== 25. T - T[] -> double[] ======================
[Fact]
public void Minus_T_TArray_Returns_DoubleArray()
{
var result = scalarUnit - unitsArray;
Assert.Equal(2, result.Length);
Assert.Equal(1000 - 1000, result[0]._Value, Tolerance);
Assert.Equal(1000 - 1, result[1]._Value, Tolerance);
}
// Дополнительно: перегрузки, где результат double (без указания R)
// Тестируем Minus<T>(T[] units, T subtrahend) возвращает double[]
[Fact]
public void Minus_TArray_T_Returns_DoubleArray()
{
var result = unitsArray - scalarUnit;
Assert.Equal(2, result.Length);
Assert.Equal(1000 - 1000, result[0]._Value, Tolerance);
Assert.Equal(1 - 1000, result[1]._Value, Tolerance);
}
[Fact]
public void Minus_ListT_T_Returns_DoubleList()
{
var result = unitsList - scalarUnit;
Assert.Equal(2, result.Count);
Assert.Equal(1000 - 1000, result[0]._Value, Tolerance);
Assert.Equal(1 - 1000, result[1]._Value, Tolerance);
}
// Проверка пустых коллекций
[Fact]
public void Minus_EmptyArray_ReturnsEmptyArray()
{
var empty = Array.Empty<Length>();
var result = empty.Minus<Length>(5.0);
Assert.Empty(result);
}
[Fact]
public void Minus_NullArray_ReturnsNull()
{
Length[] nullArray = null!;
var result = nullArray.Minus<Length>(5.0);
Assert.Null(result);
}
[Fact]
public void Minus_NullList_ReturnsNull()
{
List<Length> nullList = null!;
var result = nullList.Minus<Length>(5.0);
Assert.Null(result);
}
}
}

View File

@@ -0,0 +1,335 @@
namespace QWERTYkez.Mensura.Tests
{
public class CollectionsMultiplyExtensionsTests
{
private const double Tolerance = 1e-12;
private static readonly Length scalarUnit = Length.Meter; // 1000 mm
private static readonly double scalarDouble = 2.0;
// Коллекции единиц
private static readonly Length[] unitsArray = [Length.Meter, Length._MilliMeter];
private static readonly Length?[] nullableUnitsArray = [Length.Meter, null, Length._MilliMeter];
private static readonly List<Length> unitsList = [Length.Meter, Length._MilliMeter];
private static readonly List<Length?> nullableUnitsList = [Length.Meter, null, Length._MilliMeter];
// Коллекции double
private static readonly double[] doubleArray = [5.0, 3.0];
private static readonly double?[] nullableDoubleArray = [5.0, null, 3.0];
private static readonly List<double> doubleList = [5.0, 3.0];
private static readonly List<double?> nullableDoubleList = [5.0, null, 3.0];
// ====================== 1. T[] * double ======================
[Fact]
public void Mul_TArray_Double_Returns_TArray()
{
var result = unitsArray.Mul<Length>(scalarDouble);
Assert.Equal(2, result.Length);
Assert.Equal(1000 * 2, (double)result[0], Tolerance);
Assert.Equal(1 * 2, (double)result[1], Tolerance);
}
[Fact]
public void Mul_TArray_Double_ByZero_Works()
{
var result = unitsArray.Mul<Length>(0.0);
Assert.Equal(0, (double)result[0], Tolerance);
Assert.Equal(0, (double)result[1], Tolerance);
}
// ====================== 2. T?[] * double ======================
[Fact]
public void Mul_NullableTArray_Double_Returns_NullableTArray()
{
var result = nullableUnitsArray.Mul<Length>(scalarDouble);
Assert.Equal(3, result.Length);
Assert.Equal(1000 * 2, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // null * 2 = null
Assert.Equal(1 * 2, (double)result[2]!, Tolerance);
}
// ====================== 3. double * T[] ======================
[Fact]
public void Mul_Double_TArray_Returns_TArray()
{
var result = scalarDouble.Mul<Length>(unitsArray);
Assert.Equal(2, result.Length);
Assert.Equal(2 * 1000, (double)result[0], Tolerance);
Assert.Equal(2 * 1, (double)result[1], Tolerance);
}
// ====================== 4. double * T?[] ======================
[Fact]
public void Mul_Double_NullableTArray_Returns_NullableTArray()
{
var result = scalarDouble.Mul<Length>(nullableUnitsArray);
Assert.Equal(3, result.Length);
Assert.Equal(2 * 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // 2 * null = null
Assert.Equal(2 * 1, (double)result[2]!, Tolerance);
}
// ====================== 5. List<T> * double ======================
[Fact]
public void Mul_ListT_Double_Returns_ListT()
{
var result = unitsList.Mul<Length>(scalarDouble);
Assert.Equal(2, result.Count);
Assert.Equal(1000 * 2, (double)result[0], Tolerance);
Assert.Equal(1 * 2, (double)result[1], Tolerance);
}
// ====================== 6. List<T?> * double ======================
[Fact]
public void Mul_ListNullableT_Double_Returns_ListNullableT()
{
var result = nullableUnitsList.Mul<Length>(scalarDouble);
Assert.Equal(3, result.Count);
Assert.Equal(1000 * 2, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // null * 2 = null
Assert.Equal(1 * 2, (double)result[2]!, Tolerance);
}
// ====================== 7. double * List<T> ======================
[Fact]
public void Mul_Double_ListT_Returns_ListT()
{
var result = scalarDouble.Mul<Length>(unitsList);
Assert.Equal(2, result.Count);
Assert.Equal(2 * 1000, (double)result[0], Tolerance);
Assert.Equal(2 * 1, (double)result[1], Tolerance);
}
// ====================== 8. double * List<T?> ======================
[Fact]
public void Mul_Double_ListNullableT_Returns_ListNullableT()
{
var result = scalarDouble.Mul<Length>(nullableUnitsList);
Assert.Equal(3, result.Count);
Assert.Equal(2 * 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // 2 * null = null
Assert.Equal(2 * 1, (double)result[2]!, Tolerance);
}
// ====================== 9. IReadOnlyCollection<T> * double (Span) ======================
[Fact]
public void Mul_IReadOnlyCollectionT_Double_Span()
{
Span<Length> dest = new Length[2];
((IReadOnlyCollection<Length>)unitsArray).Mul<Length>(scalarDouble, dest);
Assert.Equal(1000 * 2, (double)dest[0], Tolerance);
Assert.Equal(1 * 2, (double)dest[1], Tolerance);
}
// ====================== 10. IReadOnlyCollection<T?> * double (Span) ======================
[Fact]
public void Mul_IReadOnlyCollectionNullableT_Double_Span()
{
Span<Length?> dest = new Length?[3];
((IReadOnlyCollection<Length?>)nullableUnitsArray).Mul<Length>(scalarDouble, dest);
Assert.Equal(1000 * 2, (double)dest[0]!, Tolerance);
Assert.Null(dest[1]); // null * 2 = null
Assert.Equal(1 * 2, (double)dest[2]!, Tolerance);
}
// ====================== 11. double * IReadOnlyCollection<T> (Span) ======================
[Fact]
public void Mul_Double_IReadOnlyCollectionT_Span()
{
Span<Length> dest = new Length[2];
scalarDouble.Mul<Length>((IReadOnlyCollection<Length>)unitsArray, dest);
Assert.Equal(2 * 1000, (double)dest[0], Tolerance);
Assert.Equal(2 * 1, (double)dest[1], Tolerance);
}
// ====================== 12. double * IReadOnlyCollection<T?> (Span) ======================
[Fact]
public void Mul_Double_IReadOnlyCollectionNullableT_Span()
{
Span<Length?> dest = new Length?[3];
scalarDouble.Mul<Length>((IReadOnlyCollection<Length?>)nullableUnitsArray, dest);
Assert.Equal(2 * 1000, (double)dest[0]!, Tolerance);
Assert.Null(dest[1]); // 2 * null = null
Assert.Equal(2 * 1, (double)dest[2]!, Tolerance);
}
// ====================== 13. IEnumerable<T> * double ======================
[Fact]
public void Mul_IEnumerableT_Double_Returns_IEnumerableT()
{
var result = ((IEnumerable<Length>)unitsArray).Mul<Length>(scalarDouble).ToList();
Assert.Equal(2, result.Count);
Assert.Equal(1000 * 2, (double)result[0], Tolerance);
Assert.Equal(1 * 2, (double)result[1], Tolerance);
}
// ====================== 14. IEnumerable<T?> * double ======================
[Fact]
public void Mul_IEnumerableNullableT_Double_Returns_IEnumerableNullableT()
{
var result = ((IEnumerable<Length?>)nullableUnitsArray).Mul<Length>(scalarDouble).ToList();
Assert.Equal(3, result.Count);
Assert.Equal(1000 * 2, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // null * 2 = null
Assert.Equal(1 * 2, (double)result[2]!, Tolerance);
}
// ====================== 15. double * IEnumerable<T> ======================
[Fact]
public void Mul_Double_IEnumerableT_Returns_IEnumerableT()
{
var result = scalarDouble.Mul<Length>((IEnumerable<Length>)unitsArray).ToList();
Assert.Equal(2, result.Count);
Assert.Equal(2 * 1000, (double)result[0], Tolerance);
Assert.Equal(2 * 1, (double)result[1], Tolerance);
}
// ====================== 16. double * IEnumerable<T?> ======================
[Fact]
public void Mul_Double_IEnumerableNullableT_Returns_IEnumerableNullableT()
{
var result = scalarDouble.Mul<Length>((IEnumerable<Length?>)nullableUnitsArray).ToList();
Assert.Equal(3, result.Count);
Assert.Equal(2 * 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // 2 * null = null
Assert.Equal(2 * 1, (double)result[2]!, Tolerance);
}
// ====================== 17. double[] * T ======================
[Fact]
public void Mul_DoubleArray_T_Returns_TArray()
{
var result = doubleArray.Mul(scalarUnit);
Assert.Equal(2, result.Length);
Assert.Equal(5 * 1000, (double)result[0], Tolerance);
Assert.Equal(3 * 1000, (double)result[1], Tolerance);
}
// ====================== 18. double?[] * T ======================
[Fact]
public void Mul_NullableDoubleArray_T_Returns_NullableTArray()
{
var result = nullableDoubleArray.Mul(scalarUnit);
Assert.Equal(3, result.Length);
Assert.Equal(5 * 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // null * Length = null
Assert.Equal(3 * 1000, (double)result[2]!, Tolerance);
}
// ====================== 19. T * double[] ======================
[Fact]
public void Mul_T_DoubleArray_Returns_TArray()
{
var result = scalarUnit.Mul(doubleArray);
Assert.Equal(2, result.Length);
Assert.Equal(1000 * 5, (double)result[0], Tolerance);
Assert.Equal(1000 * 3, (double)result[1], Tolerance);
}
// ====================== 20. T * double?[] ======================
[Fact]
public void Mul_T_NullableDoubleArray_Returns_NullableTArray()
{
var result = scalarUnit.Mul(nullableDoubleArray);
Assert.Equal(3, result.Length);
Assert.Equal(1000 * 5, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // Length * null = null
Assert.Equal(1000 * 3, (double)result[2]!, Tolerance);
}
// ====================== 21. List<double> * T ======================
[Fact]
public void Mul_ListDouble_T_Returns_ListT()
{
var result = doubleList.Mul(scalarUnit);
Assert.Equal(2, result.Count);
Assert.Equal(5 * 1000, (double)result[0], Tolerance);
Assert.Equal(3 * 1000, (double)result[1], Tolerance);
}
// ====================== 22. List<double?> * T ======================
[Fact]
public void Mul_ListNullableDouble_T_Returns_ListNullableT()
{
var result = nullableDoubleList.Mul(scalarUnit);
Assert.Equal(3, result.Count);
Assert.Equal(5 * 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // null * Length = null
Assert.Equal(3 * 1000, (double)result[2]!, Tolerance);
}
// ====================== 23. T * List<double> ======================
[Fact]
public void Mul_T_ListDouble_Returns_ListT()
{
var result = scalarUnit.Mul(doubleList);
Assert.Equal(2, result.Count);
Assert.Equal(1000 * 5, (double)result[0], Tolerance);
Assert.Equal(1000 * 3, (double)result[1], Tolerance);
}
// ====================== 24. T * List<double?> ======================
[Fact]
public void Mul_T_ListNullableDouble_Returns_ListNullableT()
{
var result = scalarUnit.Mul(nullableDoubleList);
Assert.Equal(3, result.Count);
Assert.Equal(1000 * 5, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // Length * null = null
Assert.Equal(1000 * 3, (double)result[2]!, Tolerance);
}
// ====================== 25. T * T[] -> double[] ======================
[Fact]
public void Mul_T_TArray_Returns_DoubleArray()
{
var result = scalarUnit * unitsArray;
Assert.Equal(2, result.Length);
Assert.Equal(1000 * 1000, result[0]._Value, Tolerance);
Assert.Equal(1000 * 1, result[1]._Value, Tolerance);
}
// Дополнительно: перегрузки с результатом double (без R)
[Fact]
public void Mul_TArray_T_Returns_DoubleArray()
{
var result = unitsArray * scalarUnit;
Assert.Equal(2, result.Length);
Assert.Equal(1000 * 1000, result[0]._Value, Tolerance);
Assert.Equal(1 * 1000, result[1]._Value, Tolerance);
}
[Fact]
public void Mul_ListT_T_Returns_DoubleList()
{
var result = unitsList * scalarUnit;
Assert.Equal(2, result.Count);
Assert.Equal(1000 * 1000, result[0]._Value, Tolerance);
Assert.Equal(1 * 1000, result[1]._Value, Tolerance);
}
// ====================== Обработка null коллекций ======================
[Fact]
public void Mul_NullArray_ReturnsNull()
{
Length[] nullArray = null!;
var result = nullArray.Mul<Length>(5.0);
Assert.Null(result);
}
[Fact]
public void Mul_NullList_ReturnsNull()
{
List<Length> nullList = null!;
var result = nullList.Mul<Length>(5.0);
Assert.Null(result);
}
[Fact]
public void Mul_EmptyArray_ReturnsEmptyArray()
{
var empty = Array.Empty<Length>();
var result = empty.Mul<Length>(5.0);
Assert.Empty(result);
}
}
}

View File

@@ -0,0 +1,334 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QWERTYkez.Mensura.Units;
using QWERTYkez.Mensura.Extensions;
using Xunit;
namespace QWERTYkez.Mensura.Tests
{
public class CollectionsPlusExtensionsTests
{
private const double Tolerance = 1e-12;
private static readonly Length scalarUnit = Length.Meter; // 1000 mm
private static readonly double scalarDouble = 500.0; // прибавляемое значение в мм
// Коллекции единиц
private static readonly Length[] unitsArray = new[] { Length.Meter, Length._MilliMeter };
private static readonly Length?[] nullableUnitsArray = new Length?[] { Length.Meter, null, Length._MilliMeter };
private static readonly List<Length> unitsList = new List<Length> { Length.Meter, Length._MilliMeter };
private static readonly List<Length?> nullableUnitsList = new List<Length?> { Length.Meter, null, Length._MilliMeter };
// Коллекции double
private static readonly double[] doubleArray = new double[] { 200.0, 300.0 };
private static readonly double?[] nullableDoubleArray = new double?[] { 200.0, null, 300.0 };
private static readonly List<double> doubleList = new List<double> { 200.0, 300.0 };
private static readonly List<double?> nullableDoubleList = new List<double?> { 200.0, null, 300.0 };
// ====================== 1. T[] + double ======================
[Fact]
public void Plus_TArray_Double_Returns_TArray()
{
var result = unitsArray.Plus<Length>(scalarDouble);
Assert.Equal(2, result.Length);
Assert.Equal(1000 + 500, (double)result[0], Tolerance);
Assert.Equal(1 + 500, (double)result[1], Tolerance);
}
// ====================== 2. T?[] + double ======================
[Fact]
public void Plus_NullableTArray_Double_Returns_NullableTArray()
{
var result = nullableUnitsArray.Plus<Length>(scalarDouble);
Assert.Equal(3, result.Length);
Assert.Equal(1000 + 500, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // null + число = null
Assert.Equal(1 + 500, (double)result[2]!, Tolerance);
}
// ====================== 3. double + T[] ======================
[Fact]
public void Plus_Double_TArray_Returns_TArray()
{
var result = scalarDouble.Plus<Length>(unitsArray);
Assert.Equal(2, result.Length);
Assert.Equal(500 + 1000, (double)result[0], Tolerance);
Assert.Equal(500 + 1, (double)result[1], Tolerance);
}
// ====================== 4. double + T?[] ======================
[Fact]
public void Plus_Double_NullableTArray_Returns_NullableTArray()
{
var result = scalarDouble.Plus<Length>(nullableUnitsArray);
Assert.Equal(3, result.Length);
Assert.Equal(500 + 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // число + null = null
Assert.Equal(500 + 1, (double)result[2]!, Tolerance);
}
// ====================== 5. List<T> + double ======================
[Fact]
public void Plus_ListT_Double_Returns_ListT()
{
var result = unitsList.Plus<Length>(scalarDouble);
Assert.Equal(2, result.Count);
Assert.Equal(1000 + 500, (double)result[0], Tolerance);
Assert.Equal(1 + 500, (double)result[1], Tolerance);
}
// ====================== 6. List<T?> + double ======================
[Fact]
public void Plus_ListNullableT_Double_Returns_ListNullableT()
{
var result = nullableUnitsList.Plus<Length>(scalarDouble);
Assert.Equal(3, result.Count);
Assert.Equal(1000 + 500, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // null + число = null
Assert.Equal(1 + 500, (double)result[2]!, Tolerance);
}
// ====================== 7. double + List<T> ======================
[Fact]
public void Plus_Double_ListT_Returns_ListT()
{
var result = scalarDouble.Plus<Length>(unitsList);
Assert.Equal(2, result.Count);
Assert.Equal(500 + 1000, (double)result[0], Tolerance);
Assert.Equal(500 + 1, (double)result[1], Tolerance);
}
// ====================== 8. double + List<T?> ======================
[Fact]
public void Plus_Double_ListNullableT_Returns_ListNullableT()
{
var result = scalarDouble.Plus<Length>(nullableUnitsList);
Assert.Equal(3, result.Count);
Assert.Equal(500 + 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // число + null = null
Assert.Equal(500 + 1, (double)result[2]!, Tolerance);
}
// ====================== 9. IReadOnlyCollection<T> + double (Span) ======================
[Fact]
public void Plus_IReadOnlyCollectionT_Double_Span()
{
Span<Length> dest = new Length[2];
((IReadOnlyCollection<Length>)unitsArray).Plus<Length>(scalarDouble, dest);
Assert.Equal(1000 + 500, (double)dest[0], Tolerance);
Assert.Equal(1 + 500, (double)dest[1], Tolerance);
}
// ====================== 10. IReadOnlyCollection<T?> + double (Span) ======================
[Fact]
public void Plus_IReadOnlyCollectionNullableT_Double_Span()
{
Span<Length?> dest = new Length?[3];
((IReadOnlyCollection<Length?>)nullableUnitsArray).Plus<Length>(scalarDouble, dest);
Assert.Equal(1000 + 500, (double)dest[0]!, Tolerance);
Assert.Null(dest[1]); // null + число = null
Assert.Equal(1 + 500, (double)dest[2]!, Tolerance);
}
// ====================== 11. double + IReadOnlyCollection<T> (Span) ======================
[Fact]
public void Plus_Double_IReadOnlyCollectionT_Span()
{
Span<Length> dest = new Length[2];
scalarDouble.Plus<Length>((IReadOnlyCollection<Length>)unitsArray, dest);
Assert.Equal(500 + 1000, (double)dest[0], Tolerance);
Assert.Equal(500 + 1, (double)dest[1], Tolerance);
}
// ====================== 12. double + IReadOnlyCollection<T?> (Span) ======================
[Fact]
public void Plus_Double_IReadOnlyCollectionNullableT_Span()
{
Span<Length?> dest = new Length?[3];
scalarDouble.Plus<Length>((IReadOnlyCollection<Length?>)nullableUnitsArray, dest);
Assert.Equal(500 + 1000, (double)dest[0]!, Tolerance);
Assert.Null(dest[1]); // число + null = null
Assert.Equal(500 + 1, (double)dest[2]!, Tolerance);
}
// ====================== 13. IEnumerable<T> + double ======================
[Fact]
public void Plus_IEnumerableT_Double_Returns_IEnumerableT()
{
var result = ((IEnumerable<Length>)unitsArray).Plus<Length>(scalarDouble).ToList();
Assert.Equal(2, result.Count);
Assert.Equal(1000 + 500, (double)result[0], Tolerance);
Assert.Equal(1 + 500, (double)result[1], Tolerance);
}
// ====================== 14. IEnumerable<T?> + double ======================
[Fact]
public void Plus_IEnumerableNullableT_Double_Returns_IEnumerableNullableT()
{
var result = ((IEnumerable<Length?>)nullableUnitsArray).Plus<Length>(scalarDouble).ToList();
Assert.Equal(3, result.Count);
Assert.Equal(1000 + 500, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // null + число = null
Assert.Equal(1 + 500, (double)result[2]!, Tolerance);
}
// ====================== 15. double + IEnumerable<T> ======================
[Fact]
public void Plus_Double_IEnumerableT_Returns_IEnumerableT()
{
var result = scalarDouble.Plus<Length>((IEnumerable<Length>)unitsArray).ToList();
Assert.Equal(2, result.Count);
Assert.Equal(500 + 1000, (double)result[0], Tolerance);
Assert.Equal(500 + 1, (double)result[1], Tolerance);
}
// ====================== 16. double + IEnumerable<T?> ======================
[Fact]
public void Plus_Double_IEnumerableNullableT_Returns_IEnumerableNullableT()
{
var result = scalarDouble.Plus<Length>((IEnumerable<Length?>)nullableUnitsArray).ToList();
Assert.Equal(3, result.Count);
Assert.Equal(500 + 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // число + null = null
Assert.Equal(500 + 1, (double)result[2]!, Tolerance);
}
// ====================== 17. double[] + T ======================
[Fact]
public void Plus_DoubleArray_T_Returns_TArray()
{
var result = doubleArray.Plus(scalarUnit);
Assert.Equal(2, result.Length);
Assert.Equal(200 + 1000, (double)result[0], Tolerance);
Assert.Equal(300 + 1000, (double)result[1], Tolerance);
}
// ====================== 18. double?[] + T ======================
[Fact]
public void Plus_NullableDoubleArray_T_Returns_NullableTArray()
{
var result = nullableDoubleArray.Plus(scalarUnit);
Assert.Equal(3, result.Length);
Assert.Equal(200 + 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // null + Length = null
Assert.Equal(300 + 1000, (double)result[2]!, Tolerance);
}
// ====================== 19. T + double[] ======================
[Fact]
public void Plus_T_DoubleArray_Returns_TArray()
{
var result = scalarUnit.Plus(doubleArray);
Assert.Equal(2, result.Length);
Assert.Equal(1000 + 200, (double)result[0], Tolerance);
Assert.Equal(1000 + 300, (double)result[1], Tolerance);
}
// ====================== 20. T + double?[] ======================
[Fact]
public void Plus_T_NullableDoubleArray_Returns_NullableTArray()
{
var result = scalarUnit.Plus(nullableDoubleArray);
Assert.Equal(3, result.Length);
Assert.Equal(1000 + 200, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // Length + null = null
Assert.Equal(1000 + 300, (double)result[2]!, Tolerance);
}
// ====================== 21. List<double> + T ======================
[Fact]
public void Plus_ListDouble_T_Returns_ListT()
{
var result = doubleList.Plus(scalarUnit);
Assert.Equal(2, result.Count);
Assert.Equal(200 + 1000, (double)result[0], Tolerance);
Assert.Equal(300 + 1000, (double)result[1], Tolerance);
}
// ====================== 22. List<double?> + T ======================
[Fact]
public void Plus_ListNullableDouble_T_Returns_ListNullableT()
{
var result = nullableDoubleList.Plus(scalarUnit);
Assert.Equal(3, result.Count);
Assert.Equal(200 + 1000, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // null + Length = null
Assert.Equal(300 + 1000, (double)result[2]!, Tolerance);
}
// ====================== 23. T + List<double> ======================
[Fact]
public void Plus_T_ListDouble_Returns_ListT()
{
var result = scalarUnit.Plus(doubleList);
Assert.Equal(2, result.Count);
Assert.Equal(1000 + 200, (double)result[0], Tolerance);
Assert.Equal(1000 + 300, (double)result[1], Tolerance);
}
// ====================== 24. T + List<double?> ======================
[Fact]
public void Plus_T_ListNullableDouble_Returns_ListNullableT()
{
var result = scalarUnit.Plus(nullableDoubleList);
Assert.Equal(3, result.Count);
Assert.Equal(1000 + 200, (double)result[0]!, Tolerance);
Assert.Null(result[1]); // Length + null = null
Assert.Equal(1000 + 300, (double)result[2]!, Tolerance);
}
// ====================== 25. T + T[] -> double[] ======================
[Fact]
public void Plus_T_TArray_Returns_DoubleArray()
{
var result = scalarUnit + unitsArray;
Assert.Equal(2, result.Length);
Assert.Equal(1000 + 1000, result[0]._Value, Tolerance);
Assert.Equal(1000 + 1, result[1]._Value, Tolerance);
}
// Дополнительно: перегрузки с результатом double (без R)
[Fact]
public void Plus_TArray_T_Returns_DoubleArray()
{
var result = unitsArray + scalarUnit;
Assert.Equal(2, result.Length);
Assert.Equal(1000 + 1000, result[0]._Value, Tolerance);
Assert.Equal(1 + 1000, result[1]._Value, Tolerance);
}
[Fact]
public void Plus_ListT_T_Returns_DoubleList()
{
var result = unitsList + scalarUnit;
Assert.Equal(2, result.Count);
Assert.Equal(1000 + 1000, result[0]._Value, Tolerance);
Assert.Equal(1 + 1000, result[1]._Value, Tolerance);
}
// ====================== Обработка null коллекций ======================
[Fact]
public void Plus_NullArray_ReturnsNull()
{
Length[] nullArray = null;
var result = nullArray.Plus<Length>(5.0);
Assert.Null(result);
}
[Fact]
public void Plus_NullList_ReturnsNull()
{
List<Length> nullList = null;
var result = nullList.Plus<Length>(5.0);
Assert.Null(result);
}
[Fact]
public void Plus_EmptyArray_ReturnsEmptyArray()
{
var empty = Array.Empty<Length>();
var result = empty.Plus<Length>(5.0);
Assert.Empty(result);
}
}
}

View File

@@ -0,0 +1,73 @@
namespace QWERTYkez.Mensura.Tests;
public class CollectionsPow2Extensions
{
[Fact]
public void Pow2_Array_CalculatesCorrectly()
{
// Arrange
Length[] source = [new(2), new(3), new(10)];
// Act
Length[] result = source.Pow2<Length, Length>();
// Assert
Assert.Equal(4, result[0]._Value);
Assert.Equal(9, result[1]._Value);
Assert.Equal(100, result[2]._Value);
}
[Fact]
public void Pow2_NullableArray_HandlesNulls()
{
// Arrange
Length?[] source = [new(5), null, new(4)];
// Act
Length?[] result = source.Pow2<Length, Length>();
// Assert
Assert.Equal(25, result[0]?._Value);
Assert.Null(result[1]);
Assert.Equal(16, result[2]?._Value);
}
[Fact]
public void Pow2_List_ReturnsCorrectResult()
{
// Arrange
var source = new List<Length> { new(2), new(8) };
// Act
var result = source.Pow2<Length, Length>();
// Assert
Assert.Equal(4, result[0]._Value);
Assert.Equal(64, result[1]._Value);
}
[Fact]
public void Pow2_IEnumerable_ProcessesCorrectly()
{
// Arrange
IEnumerable<Length> source = new HashSet<Length> { new(3), new(7) };
// Act
var result = source.Pow2<Length, Length>().ToList();
// Assert
Assert.Contains(result, x => x._Value == 9);
Assert.Contains(result, x => x._Value == 49);
}
[Fact]
public void Pow2_DestinationTooShort_ThrowsArgumentException()
{
// Arrange
Length[] source = [new(2), new(3)];
Length[] dest = new Length[1];
// Act & Assert
Assert.Throws<ArgumentException>(() => source.Pow2(dest));
}
}

View File

@@ -0,0 +1,59 @@
namespace QWERTYkez.Mensura.Tests;
public class Pow3ExtensionsTests
{
[Fact]
public void Pow3_Array_CalculatesCorrectly()
{
// Arrange: 2^3=8, 3^3=27, 4^3=64
Length[] source = [new(2), new(3), new(4)];
// Act
Length[] result = source.Pow3<Length, Length>();
// Assert
Assert.Equal(8, result[0]._Value);
Assert.Equal(27, result[1]._Value);
Assert.Equal(64, result[2]._Value);
}
[Fact]
public void Pow3_NullableArray_HandlesNulls()
{
// Arrange
Length?[] source = [new(5), null, new(10)];
// Act
Length?[] result = source.Pow3<Length, Length>();
// Assert
Assert.Equal(125, result[0]?._Value);
Assert.Null(result[1]);
Assert.Equal(1000, result[2]?._Value);
}
[Fact]
public void Pow3_List_ReturnsCorrectResult()
{
// Arrange
var source = new List<Length> { new(2), new(5) };
// Act
var result = source.Pow3<Length, Length>();
// Assert
Assert.Equal(8, result[0]._Value);
Assert.Equal(125, result[1]._Value);
}
[Fact]
public void Pow3_DestinationTooShort_ThrowsArgumentException()
{
// Arrange
Length[] source = [new(2), new(3)];
Length[] dest = new Length[1];
// Act & Assert
Assert.Throws<ArgumentException>(() => source.Pow3(dest));
}
}

View File

@@ -0,0 +1,75 @@
namespace QWERTYkez.Mensura.Tests;
public class CollectionsPowNExtensions
{
private const double BaseVal = 3.0;
private const double Expected = 9.0; // 3^2 = 9
[Fact]
public void Test_PowN()
{
Length[] arr = [new(BaseVal)];
Length?[] arrNull = [new(BaseVal), null];
List<Length> list = [new(BaseVal)];
List<Length?> listNull = [new(BaseVal), null];
IReadOnlyCollection<Length> roc = list;
IEnumerable<Length> en = list;
IEnumerable<Length?> enNull = listNull;
// --- 1. Целые степени (int) ---
// Span-based Core
Span<Length> dstSpan = new Length[1];
arr.AsSpan().PowCore(2, 1, dstSpan);
Assert.Equal(Expected, dstSpan[0]._Value);
arrNull.AsSpan().PowCore(2, 2, new Length?[2]); // Проверка nullable span
arr.AsSpan().Pow(2, dstSpan);
Assert.Equal(Expected, dstSpan[0]._Value);
// Arrays
Assert.Equal(Expected, arr.Pow<Length, Length>(2)[0]._Value);
Assert.Equal(Expected, arrNull.Pow<Length, Length>(2)[0]?._Value);
// Lists
Assert.Equal(Expected, list.Pow<Length, Length>(2)[0]._Value);
Assert.Equal(Expected, listNull.Pow<Length, Length>(2)[0]?._Value);
// ReadOnlyCollection
roc.Pow(2, dstSpan);
Assert.Equal(Expected, dstSpan[0]._Value);
// IEnumerable / Iterators
Assert.Equal(Expected, en.Pow<Length, Length>(2).First()._Value);
Assert.Equal(Expected, enNull.Pow<Length, Length>(2).First(x => x.HasValue)?._Value);
// --- 2. Дробные степени (double) ---
// Span-based Core
arr.AsSpan().PowCore(2.0, 1, dstSpan);
Assert.Equal(Expected, dstSpan[0]._Value);
arr.AsSpan().Pow(2.0, dstSpan);
Assert.Equal(Expected, dstSpan[0]._Value);
// Arrays
Assert.Equal(Expected, arr.Pow<Length, Length>(2.0)[0]._Value);
Assert.Equal(Expected, arrNull.Pow<Length, Length>(2.0)[0]?._Value);
// Lists
Assert.Equal(Expected, list.Pow<Length, Length>(2.0)[0]._Value);
Assert.Equal(Expected, listNull.Pow<Length, Length>(2.0)[0]?._Value);
// ReadOnlyCollection
roc.Pow(2.0, dstSpan);
Assert.Equal(Expected, dstSpan[0]._Value);
// IEnumerable / Iterators
Assert.Equal(Expected, en.Pow<Length, Length>(2.0).First()._Value);
Assert.Equal(Expected, enNull.Pow<Length, Length>(2.0).First(x => x.HasValue)?._Value);
// Дополнительные итераторы
Assert.Equal(Expected, en.PowIterator<Length, Length>(2.0).First()._Value);
Assert.Equal(Expected, enNull.PowNullableIterator<Length, Length>(2.0).First(x => x.HasValue)?._Value);
}
}

View File

@@ -0,0 +1,59 @@
namespace QWERTYkez.Mensura.Tests;
public class CollectionsRootOfCubeExtensions
{
[Fact]
public void Cbrt_Array_CalculatesCorrectly()
{
Length[] source = [new(4), new(9), new(16), new(25)];
// Ожидаем корень, но важно: Math.Sqrt(4) = 2.
// Если ваш код делает специфичные преобразования, адаптируйте Assert.
var result = source.Cbrt<Length, Length>();
Assert.Equal(2, result[0]._Value);
Assert.Equal(3, result[1]._Value);
Assert.Equal(4, result[2]._Value);
Assert.Equal(5, result[3]._Value);
}
[Fact]
public void Cbrt_NullableArray_HandlesNulls()
{
Length?[] source = [new(16), null, new(36)];
var result = source.Cbrt<Length, Length>();
Assert.Equal(4, result[0]?._Value);
Assert.Null(result[1]);
Assert.Equal(6, result[2]?._Value);
}
[Fact]
public void Cbrt_DestinationTooShort_ThrowsArgumentException()
{
Length[] source = [new(1), new(4)];
Length[] dest = new Length[1];
Assert.Throws<ArgumentException>(() => source.Cbrt(dest));
}
[Fact]
public void Cbrt_List_WorksCorrectly()
{
var list = new List<Length> { new(1), new(4) };
var result = list.Cbrt<Length, Length>();
Assert.Equal(2, result.Count);
Assert.Equal(1, result[0]._Value);
Assert.Equal(2, result[1]._Value);
}
[Fact]
public void Cbrt_IEnumerable_NoExtraAllocations()
{
IEnumerable<Length> source = new HashSet<Length> { new(9), new(81) };
var result = source.Cbrt<Length, Length>().ToList();
Assert.Contains(result, x => x._Value == 3);
Assert.Contains(result, x => x._Value == 9);
}
}

View File

@@ -0,0 +1,75 @@
namespace QWERTYkez.Mensura.Tests;
public class CollectionsRootOfSquareExtensions
{
[Fact]
public void Sqrt_Array_ShouldCalculateCorrectly()
{
// Arrange
Length[] source = [new(4), new(9), new(16)];
// Act
Length[] result = source.Sqrt<Length, Length>();
// Assert
Assert.Equal(2, result[0]._Value);
Assert.Equal(3, result[1]._Value);
Assert.Equal(4, result[2]._Value);
}
[Fact]
public void Sqrt_NullableArray_ShouldHandleNulls()
{
// Arrange
Length?[] source = [new(25), null, new(100)];
// Act
Length?[] result = source.Sqrt<Length, Length>();
// Assert
Assert.Equal(5, result[0]?._Value);
Assert.Null(result[1]);
Assert.Equal(10, result[2]?._Value);
}
[Fact]
public void Sqrt_List_ShouldReturnCorrectList()
{
// Arrange
var source = new List<Length> { new(1), new(4), new(9) };
// Act
var result = source.Sqrt<Length, Length>();
// Assert
Assert.Equal(3, result.Count);
Assert.Equal(1, result[0]._Value);
Assert.Equal(2, result[1]._Value);
Assert.Equal(3, result[2]._Value);
}
[Fact]
public void Sqrt_DestinationTooShort_ShouldThrowArgumentException()
{
// Arrange
Length[] source = [new(4), new(9)];
Length[] dest = new Length[1]; // Слишком мало
// Act & Assert
Assert.Throws<ArgumentException>(() => source.AsSpan().Sqrt(dest.AsSpan()));
}
[Fact]
public void Sqrt_IEnumerable_ShouldWorkWithGenericCollection()
{
// Arrange
IEnumerable<Length> source = new HashSet<Length> { new(16), new(64) };
// Act
var result = source.Sqrt<Length, Length>().ToList();
// Assert
Assert.Contains(result, x => x._Value == 4);
Assert.Contains(result, x => x._Value == 8);
}
}

View File

@@ -0,0 +1,164 @@
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
}

View File

@@ -7,6 +7,14 @@
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net10.0|AnyCPU'">
<NoWarn>1701;1702;IDE0221</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net10.0|AnyCPU'">
<NoWarn>1701;1702;IDE0221</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
@@ -23,6 +31,8 @@
<ItemGroup>
<ProjectReference Include="..\QWERTYkez.Mensura\QWERTYkez.Mensura.csproj" />
<InternalsVisibleTo Include="..\QWERTYkez.Mensura\QWERTYkez.Mensura.csproj" />
<ProjectReference Include="..\QWERTYkez.Mensura.Generator\QWERTYkez.Mensura.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

View File

@@ -0,0 +1,2 @@
global using QWERTYkez.Mensura.Extensions;
global using QWERTYkez.Mensura.Units;