diff --git a/QWERTYkez.Mensura.Generator/ComplexUnitGenerator.cs b/QWERTYkez.Mensura.Generator/ComplexUnitGenerator.cs index 0f0fc63..dcd9c43 100644 --- a/QWERTYkez.Mensura.Generator/ComplexUnitGenerator.cs +++ b/QWERTYkez.Mensura.Generator/ComplexUnitGenerator.cs @@ -407,6 +407,9 @@ public readonly partial record struct {typeNameZ} : IMensuraUnit<{typeNameZ}>, I { get => ({typeNameA})_Value; init => _Value = (double)value; } + public static explicit operator {typeNameZ}(double val) => Unsafe.As(ref val); + public static explicit operator double({typeNameZ} unit) => unit._Value; + [JsonIgnore, IgnoreDataMember] public bool IsPositive => _Value >= 0; [JsonIgnore, IgnoreDataMember] public bool IsGreaterThanZero => _Value > 0; diff --git a/QWERTYkez.Mensura.Generator/TestsGenerator.cs b/QWERTYkez.Mensura.Generator/TestsGenerator.cs new file mode 100644 index 0000000..a59c8ef --- /dev/null +++ b/QWERTYkez.Mensura.Generator/TestsGenerator.cs @@ -0,0 +1,166 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; + +namespace G; + +[Generator] +public class TestsGenerator : IIncrementalGenerator +{ + private const string UnitAttributeFullName = "QWERTYkez.Mensura.UnitGeneratorAttribute"; + private const string ComplexAttributeFullName = "QWERTYkez.Mensura.ComplexUnitGeneratorAttribute"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var compilationProvider = context.CompilationProvider; + + context.RegisterSourceOutput(compilationProvider, (spc, compilation) => + { + // Генерируем тесты только если это тестовый проект + var assemblyName = compilation.AssemblyName; + if (assemblyName == null || !assemblyName.EndsWith(".Tests")) + return; + + // Находим основную сборку + var mensuraAssembly = compilation.References + .Select(r => compilation.GetAssemblyOrModuleSymbol(r) as IAssemblySymbol) + .FirstOrDefault(a => a?.Name == "QWERTYkez.Mensura" || a?.Name?.EndsWith(".Mensura") == true); + + if (mensuraAssembly == null) + return; + + // Собираем типы с атрибутами + var targetTypes = new List(); + CollectTypesWithAttribute(mensuraAssembly.GlobalNamespace, targetTypes, UnitAttributeFullName); + CollectTypesWithAttribute(mensuraAssembly.GlobalNamespace, targetTypes, ComplexAttributeFullName); + + if (targetTypes.Count == 0) + return; + + foreach (var type in targetTypes) + { + var typeName = type.Name; + var unitProperties = GetStaticProperties(type); + var firstNonBaseProp = unitProperties.FirstOrDefault(p => !p.Name.StartsWith("_")); + + var sb = new StringBuilder(); + sb.AppendLine("// "); + sb.AppendLine("#pragma warning disable"); + sb.AppendLine(); + sb.AppendLine("using Xunit;"); + sb.AppendLine("using QWERTYkez.Mensura.Units;"); + sb.AppendLine("using QWERTYkez.Mensura.Extensions;"); + sb.AppendLine(); + sb.AppendLine($"namespace QWERTYkez.Mensura.Tests"); + sb.AppendLine("{"); + sb.AppendLine($" public class {typeName}Tests"); + sb.AppendLine(" {"); + sb.AppendLine($" private const double Tolerance = 1e-12;"); + sb.AppendLine(); + + // Тест значения по умолчанию + sb.AppendLine($" [Fact]"); + sb.AppendLine($" public void Default_ValueIsZero()"); + sb.AppendLine($" {{"); + sb.AppendLine($" var unit = new {typeName}();"); + sb.AppendLine($" Assert.Equal(0, (double)unit, Tolerance);"); + sb.AppendLine($" }}"); + sb.AppendLine(); + + // Тесты статических свойств (единиц) + foreach (var prop in unitProperties) + { + bool isBase = prop.Name.StartsWith("_"); + sb.AppendLine($" [Fact]"); + sb.AppendLine($" public void {prop.Name}_IsCorrect()"); + sb.AppendLine($" {{"); + sb.AppendLine($" var unit = {typeName}.{prop.Name};"); + if (isBase) + { + sb.AppendLine($" Assert.Equal(1, (double)unit, Tolerance);"); + } + else + { + sb.AppendLine($" Assert.True((double)unit > 0);"); + } + sb.AppendLine($" }}"); + sb.AppendLine(); + } + + // Тесты коллекционных методов + if (unitProperties.Length > 0 && firstNonBaseProp.Name != null) + { + sb.AppendLine($" [Fact]"); + sb.AppendLine($" public void CollectionOperations_Work()"); + sb.AppendLine($" {{"); + sb.AppendLine($" var first = {typeName}.{firstNonBaseProp.Name};"); + sb.AppendLine($" var items = new[] {{ first, first }};"); + sb.AppendLine($" var sum = items.Sum();"); + sb.AppendLine($" Assert.True((double)sum > 0);"); + sb.AppendLine($" var avg = items.Average();"); + sb.AppendLine($" Assert.True((double)avg > 0);"); + sb.AppendLine($" var max = items.Max();"); + sb.AppendLine($" Assert.True((double)max > 0);"); + sb.AppendLine($" var min = items.Min();"); + sb.AppendLine($" Assert.True((double)min > 0);"); + sb.AppendLine($" }}"); + sb.AppendLine(); + } + + sb.AppendLine(" }"); + sb.AppendLine("}"); + spc.AddSource($"{typeName}Tests.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8)); + } + }); + } + + private static void CollectTypesWithAttribute(INamespaceSymbol ns, List results, string attributeFullName) + { + foreach (var type in ns.GetTypeMembers()) + { + if (HasAttribute(type, attributeFullName)) + results.Add(type); + } + foreach (var childNs in ns.GetNamespaceMembers()) + { + CollectTypesWithAttribute(childNs, results, attributeFullName); + } + } + + private static bool HasAttribute(INamedTypeSymbol type, string attributeFullName) + { + foreach (var attr in type.GetAttributes()) + { + if (attr.AttributeClass?.ToString() == attributeFullName) + return true; + } + return false; + } + + private static ImmutableArray GetStaticProperties(INamedTypeSymbol type) + { + var props = new List(); + foreach (var member in type.GetMembers()) + { + if (member is IPropertySymbol prop && prop.IsStatic && prop.DeclaredAccessibility == Accessibility.Public) + { + var propName = prop.Name; + if (propName == "EqualityContract" || propName.StartsWith("<")) + continue; + props.Add(new UnitProperty(propName, prop.Type.ToString())); + } + } + return [.. props]; + } + + private readonly struct UnitProperty + { + public string Name { get; } + public string Type { get; } + public UnitProperty(string name, string type) => (Name, Type) = (name, type); + } +} \ No newline at end of file diff --git a/QWERTYkez.Mensura.Tests/QWERTYkez.Mensura.Tests.csproj b/QWERTYkez.Mensura.Tests/QWERTYkez.Mensura.Tests.csproj new file mode 100644 index 0000000..3cad762 --- /dev/null +++ b/QWERTYkez.Mensura.Tests/QWERTYkez.Mensura.Tests.csproj @@ -0,0 +1,32 @@ + + + + net10.0 + enable + enable + false + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + \ No newline at end of file diff --git a/QWERTYkez.Mensura.slnx b/QWERTYkez.Mensura.slnx index 2599336..9c4a615 100644 --- a/QWERTYkez.Mensura.slnx +++ b/QWERTYkez.Mensura.slnx @@ -1,4 +1,5 @@ + diff --git a/QWERTYkez.Mensura/Operators.cs b/QWERTYkez.Mensura/Operators.cs index 7ff9cf1..20b0858 100644 --- a/QWERTYkez.Mensura/Operators.cs +++ b/QWERTYkez.Mensura/Operators.cs @@ -31,12 +31,12 @@ public readonly partial record struct Length public static Pressure operator *(Length left, ForceVolumetric right) => right * left; [OperatorsGenerator(nameof(Coeff1))] public static Pressure operator *(ForceVolumetric left, Length right) => new(left._Value * right._Value * Coeff1); - public static readonly double Coeff1 = Coefficients.MultiplyCoefficient(ForceVolumetric.NewtonPerMeterCubic, Length.Meter, Pressure.NewtonPerMeterSquared); + internal static readonly double Coeff1 = Coefficients.MultiplyCoefficient(ForceVolumetric.NewtonPerMeterCubic, Length.Meter, Pressure.NewtonPerMeterSquared); public static Torque operator *(Force left, Length right) => right * left; [OperatorsGenerator(nameof(Coeff2))] public static Torque operator *(Length left, Force right) => new(left._Value * right._Value * Coeff2); - public static readonly double Coeff2 = Coefficients.MultiplyCoefficient(Length.Meter, Force._Newton, Torque._Newton_Meter); + internal static readonly double Coeff2 = Coefficients.MultiplyCoefficient(Length.Meter, Force._Newton, Torque._Newton_Meter); } public readonly partial record struct Mass // Grams @@ -47,27 +47,27 @@ public readonly partial record struct Mass // Grams public readonly partial record struct Pressure // Pascals { [OperatorsGenerator(nameof(Coeff1))] public static Length operator /(Pressure left, ForceVolumetric right) => new(left._Value * Coeff1 / right._Value); - public static readonly double Coeff1 = Coefficients.DivideCoefficient(Pressure._Pascal, ForceVolumetric.NewtonPerMeterCubic, Length.Meter); + internal static readonly double Coeff1 = Coefficients.DivideCoefficient(Pressure._Pascal, ForceVolumetric.NewtonPerMeterCubic, Length.Meter); public static Force operator *(Pressure left, Area right) => right * left; [OperatorsGenerator(nameof(Coeff2))] public static Force operator *(Area left, Pressure right) => new (left._Value * right._Value * Coeff2); - public static readonly double Coeff2 = Coefficients.MultiplyCoefficient(Area.MeterSquared, Pressure._Pascal, Force._Newton); + internal static readonly double Coeff2 = Coefficients.MultiplyCoefficient(Area.MeterSquared, Pressure._Pascal, Force._Newton); [OperatorsGenerator(nameof(Coeff3))] public static Length operator /(ForceLinear left, Pressure right) => new(left._Value * Coeff3 / right._Value); - public static readonly double Coeff3 = Coefficients.DivideCoefficient(ForceLinear._NewtonPerMilliMeter, Pressure.NewtonPerMilliMeterSquared, Length._MilliMeter); + internal static readonly double Coeff3 = Coefficients.DivideCoefficient(ForceLinear._NewtonPerMilliMeter, Pressure.NewtonPerMilliMeterSquared, Length._MilliMeter); public static ForceLinear operator *(Pressure left, Length right) => right * left; [OperatorsGenerator(nameof(Coeff4))] public static ForceLinear operator *(Length left, Pressure right) => new(left._Value * right._Value * Coeff4); - public static readonly double Coeff4 = Coefficients.MultiplyCoefficient(Length._MilliMeter, Pressure.NewtonPerMilliMeterSquared, ForceLinear._NewtonPerMilliMeter); + internal static readonly double Coeff4 = Coefficients.MultiplyCoefficient(Length._MilliMeter, Pressure.NewtonPerMilliMeterSquared, ForceLinear._NewtonPerMilliMeter); } public readonly partial record struct Area // MilliMetersSquared { public static Torque operator *(ForceLinear left, Area right) => right * left; [OperatorsGenerator(nameof(Coeff1))] public static Torque operator *(Area left, ForceLinear right) => new(left._Value * right._Value * Coeff1); - public static readonly double Coeff1 = Coefficients.MultiplyCoefficient(Area.MeterSquared, ForceLinear.NewtonPerMeter, Torque._Newton_Meter); + internal static readonly double Coeff1 = Coefficients.MultiplyCoefficient(Area.MeterSquared, ForceLinear.NewtonPerMeter, Torque._Newton_Meter); [OperatorsGenerator] public static Length operator /(Area left, Length right) => new(left._Value / right._Value); } @@ -76,7 +76,7 @@ public readonly partial record struct Volume // MillimetersCubic { public static Torque operator *(Pressure left, Volume right) => right * left; [OperatorsGenerator(nameof(Coeff2))] public static Torque operator *(Volume left, Pressure right) => new(left._Value * right._Value * Coeff2); - public static readonly double Coeff2 = Coefficients.MultiplyCoefficient(Volume.MeterCubic, Pressure._Pascal, Torque._Newton_Meter); + internal static readonly double Coeff2 = Coefficients.MultiplyCoefficient(Volume.MeterCubic, Pressure._Pascal, Torque._Newton_Meter); [OperatorsGenerator] public static Area operator /(Volume left, Length right) => new(left._Value / right._Value); [OperatorsGenerator] public static Length operator /(Volume left, Area right) => new(left._Value / right._Value); @@ -85,24 +85,24 @@ public readonly partial record struct Volume // MillimetersCubic public readonly partial record struct Force // Newtons { [OperatorsGenerator(nameof(Coeff1))] public static Area operator /(Force left, Pressure right) => new(left._Value * Coeff1 / right._Value); - public static readonly double Coeff1 = Coefficients.DivideCoefficient(Force._Newton, Pressure._Pascal, Area.MeterSquared); + internal static readonly double Coeff1 = Coefficients.DivideCoefficient(Force._Newton, Pressure._Pascal, Area.MeterSquared); [OperatorsGenerator(nameof(Coeff2))] public static Pressure operator /(Force left, Area right) => new(left._Value * Coeff2 / right._Value); - public static readonly double Coeff2 = Coefficients.DivideCoefficient(Force._Newton, Area.MeterSquared, Pressure._Pascal); + internal static readonly double Coeff2 = Coefficients.DivideCoefficient(Force._Newton, Area.MeterSquared, Pressure._Pascal); } public readonly partial record struct Torque // NewtonMeters { [OperatorsGenerator(nameof(Coeff1))] public static Length operator /(Torque left, Force right) => new(left._Value * Coeff1 / right._Value); - public static readonly double Coeff1 = Coefficients.DivideCoefficient(Torque._Newton_Meter, Force._Newton, Length.Meter); + internal static readonly double Coeff1 = Coefficients.DivideCoefficient(Torque._Newton_Meter, Force._Newton, Length.Meter); [OperatorsGenerator(nameof(Coeff2))] public static Force operator /(Torque left, Length right) => new(left._Value * Coeff2 / right._Value); - public static readonly double Coeff2 = Coefficients.DivideCoefficient(Torque._Newton_Meter, Length.Meter, Force._Newton); + internal static readonly double Coeff2 = Coefficients.DivideCoefficient(Torque._Newton_Meter, Length.Meter, Force._Newton); [OperatorsGenerator(nameof(Coeff3))] public static ForceLinear operator /(Torque left, Area right) => new(left._Value * Coeff3 / right._Value); - public static readonly double Coeff3 = Coefficients.DivideCoefficient(Torque._Newton_Meter, Area._MilliMeterSquared, ForceLinear.KiloNewtonPerMilliMeter ); + internal static readonly double Coeff3 = Coefficients.DivideCoefficient(Torque._Newton_Meter, Area._MilliMeterSquared, ForceLinear.KiloNewtonPerMilliMeter ); [OperatorsGenerator(nameof(Coeff4))] public static Pressure operator /(Torque left, Volume right) => new(left._Value * Coeff4 / right._Value); - public static readonly double Coeff4 = Coefficients.DivideCoefficient(Torque._Newton_Meter, Volume.MeterCubic, Pressure._Pascal); + internal static readonly double Coeff4 = Coefficients.DivideCoefficient(Torque._Newton_Meter, Volume.MeterCubic, Pressure._Pascal); [OperatorsGenerator(nameof(Coeff5))] public static Volume operator /(Torque left, Pressure right) => new(left._Value * Coeff5 / right._Value); - public static readonly double Coeff5 = Coefficients.DivideCoefficient(Torque._Newton_Meter, Pressure._Pascal, Volume.MeterCubic); + internal static readonly double Coeff5 = Coefficients.DivideCoefficient(Torque._Newton_Meter, Pressure._Pascal, Volume.MeterCubic); } public readonly partial record struct Frequency // Hertz @@ -114,42 +114,42 @@ public readonly partial record struct Time { public static Speed operator *(Boost left, Time right) => right * left; [OperatorsGenerator(nameof(Coeff1))] public static Speed operator *(Time left, Boost right) => new(left._Value * right._Value * Coeff1); - public static readonly double Coeff1 = Coefficients.MultiplyCoefficient(Time.Second, Boost._MeterPerSecondSquared, Speed.MeterPerSecond); + internal static readonly double Coeff1 = Coefficients.MultiplyCoefficient(Time.Second, Boost._MeterPerSecondSquared, Speed.MeterPerSecond); [OperatorsGenerator(nameof(Coeff2))] public static Speed operator /(Length left, Time right) => new(left._Value * Coeff2 / right._Value); - public static readonly double Coeff2 = Coefficients.DivideCoefficient(Length.Meter, Time.Second, Speed.MeterPerSecond); + internal static readonly double Coeff2 = Coefficients.DivideCoefficient(Length.Meter, Time.Second, Speed.MeterPerSecond); } public readonly partial record struct Speed { public static Length operator *(Speed left, Time right) => right * left; [OperatorsGenerator(nameof(Coeff1))] public static Length operator *(Time left, Speed right) => new(left._Value * right._Value * Coeff1); - public static readonly double Coeff1 = Coefficients.MultiplyCoefficient(Time.Second, Speed.MeterPerSecond, Length.Meter); + internal static readonly double Coeff1 = Coefficients.MultiplyCoefficient(Time.Second, Speed.MeterPerSecond, Length.Meter); [OperatorsGenerator(nameof(Coeff2))] public static Boost operator /(Speed left, Time right) => new(left._Value * Coeff2 / right._Value); - public static readonly double Coeff2 = Coefficients.DivideCoefficient(Speed.MeterPerSecond, Time.Second, Boost._MeterPerSecondSquared); + internal static readonly double Coeff2 = Coefficients.DivideCoefficient(Speed.MeterPerSecond, Time.Second, Boost._MeterPerSecondSquared); } public readonly partial record struct Boost { public static Force operator *(Mass left, Boost right) => right * left; [OperatorsGenerator(nameof(Coeff1))] public static Force operator *(Boost left, Mass right) => new(left._Value * right._Value * Coeff1); - public static readonly double Coeff1 = Coefficients.MultiplyCoefficient(Boost._MeterPerSecondSquared, Mass.KiloGram, Force._Newton); + internal static readonly double Coeff1 = Coefficients.MultiplyCoefficient(Boost._MeterPerSecondSquared, Mass.KiloGram, Force._Newton); public static ForceLinear operator *(MassLinear left, Boost right) => right * left; [OperatorsGenerator(nameof(Coeff2))] public static ForceLinear operator *(Boost left, MassLinear right) => new(left._Value * right._Value * Coeff2); - public static readonly double Coeff2 = Coefficients.MultiplyCoefficient(Boost._MeterPerSecondSquared, MassLinear.KiloGramPerMilliMeter, ForceLinear._NewtonPerMilliMeter); + internal static readonly double Coeff2 = Coefficients.MultiplyCoefficient(Boost._MeterPerSecondSquared, MassLinear.KiloGramPerMilliMeter, ForceLinear._NewtonPerMilliMeter); public static ForceVolumetric operator *(Density left, Boost right) => right * left; [OperatorsGenerator(nameof(Coeff3))] public static ForceVolumetric operator *(Boost left, Density right) => new(left._Value * right._Value * Coeff3); - public static readonly double Coeff3 = Coefficients.MultiplyCoefficient(Boost._MeterPerSecondSquared, Density.KiloGramPerMilliMeterCubic, ForceVolumetric._NewtonPerMilliMeterCubic); + internal static readonly double Coeff3 = Coefficients.MultiplyCoefficient(Boost._MeterPerSecondSquared, Density.KiloGramPerMilliMeterCubic, ForceVolumetric._NewtonPerMilliMeterCubic); [OperatorsGenerator(nameof(Coeff4))] public static MassLinear operator /(ForceLinear left, Boost right) => new(left._Value * Coeff4 / right._Value); - public static readonly double Coeff4 = Coefficients.DivideCoefficient(ForceLinear._NewtonPerMilliMeter, Boost._MeterPerSecondSquared, MassLinear.KiloGramPerMilliMeter); + internal static readonly double Coeff4 = Coefficients.DivideCoefficient(ForceLinear._NewtonPerMilliMeter, Boost._MeterPerSecondSquared, MassLinear.KiloGramPerMilliMeter); [OperatorsGenerator(nameof(Coeff5))] public static Density operator /(ForceVolumetric left, Boost right) => new(left._Value * Coeff5 / right._Value); - public static readonly double Coeff5 = Coefficients.DivideCoefficient(ForceVolumetric._NewtonPerMilliMeterCubic, Boost._MeterPerSecondSquared, Density.KiloGramPerMilliMeterCubic); + internal static readonly double Coeff5 = Coefficients.DivideCoefficient(ForceVolumetric._NewtonPerMilliMeterCubic, Boost._MeterPerSecondSquared, Density.KiloGramPerMilliMeterCubic); [OperatorsGenerator] public static MassPerSquare operator /(Pressure left, Boost right) => new(left._Value / right._Value); @@ -166,5 +166,5 @@ public readonly partial record struct MassLinear { [OperatorsGenerator(nameof(Coeff1))] public static Boost operator /(ForceLinear left, MassLinear right) => new(left._Value * Coeff1 / right._Value); - public static readonly double Coeff1 = Coefficients.DivideCoefficient(ForceLinear._NewtonPerMilliMeter, MassLinear.KiloGramPerMilliMeter, Boost._MeterPerSecondSquared); + internal static readonly double Coeff1 = Coefficients.DivideCoefficient(ForceLinear._NewtonPerMilliMeter, MassLinear.KiloGramPerMilliMeter, Boost._MeterPerSecondSquared); } \ No newline at end of file