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); } }