Files
QWERTYkez.Mensura/QWERTYkez.Mensura.Generator/TestsGenerator.cs
2026-06-08 01:20:30 +07:00

166 lines
7.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<INamedTypeSymbol>();
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("// <auto-generated/>");
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<INamedTypeSymbol> 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<UnitProperty> GetStaticProperties(INamedTypeSymbol type)
{
var props = new List<UnitProperty>();
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);
}
}