解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
大石头 authored at 2018-05-15 21:21:05
6.29 KiB
X
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using NewLife.SourceGenerator;
using Xunit;

namespace XUnitTest.SourceGenerator;

/// <summary>Http 控制器静态分发生成器测试</summary>
public class HttpControllerDispatchGeneratorTests
{
    #region 测试源码
    private const String Stub = """
namespace NewLife.Http
{
    public interface IHttpContext
    {
        IDictionary<String, Object?> Parameters { get; }
        HttpResponse Response { get; }
        IServiceProvider? ServiceProvider { get; }
    }

    public class HttpResponse
    {
        public void SetResult(Object result, String? contentType = null) { }
    }

    public class HttpDispatchTable
    {
        public delegate Task ControllerInvokeDelegate(Object controller, IHttpContext context);

        public static void RegisterController(Type type, Dictionary<String, ControllerInvokeDelegate> dispatchers) { }
    }

    public class HttpServer
    {
        public void MapController<TController>(String? path = null) { }

        public void MapController(Type controllerType, String? path = null) { }
    }
}

namespace NewLife.Reflection
{
    public static class Reflect
    {
        public static T? ChangeType<T>(Object? value) => default;
    }
}

namespace NewLife.Serialization
{
    public class JsonHelper
    {
        public static JsonHelper Default { get; } = new();

        public Object? Convert(Object value, Type type) => null;
    }
}
""";

    private const String Source = """
public class OrderController
{
    public String Info() => "info";

    public async Task<String> AsyncInfo() => await Task.FromResult("async");

    public Int32 Sum(Int32 a, Int32 b) => a + b;

    public String Echo(String name, Int32 level = 7) => name;

    public String Where(IHttpContext ctx) => "where";

    public static String StaticInfo() => "static";
}

public class OddController
{
    public String Run(Int32 id) => "a";

    public String Run(String name) => "b";

    public String Generic<T>() => "g";

    public String Ref(ref Int32 value) => "r";

    public ValueTask<String> Async() => new ValueTask<String>("v");

    public String Ok() => "ok";
}

public interface INotController
{
    String Info();
}

public class Client
{
    public void Setup(HttpServer server)
    {
        server.MapController<OrderController>("/api/order");
        server.MapController(typeof(OddController));
        server.MapController<INotController>();
    }
}
""";

    private static readonly String FullSource = "using System;" + Environment.NewLine
        + "using System.Collections.Generic;" + Environment.NewLine
        + "using System.Threading.Tasks;" + Environment.NewLine
        + "using NewLife.Http;" + Environment.NewLine
        + Stub + Environment.NewLine + Source;
    #endregion

    #region 测试
    /// <summary>扫描 MapController 注册点,为控制器方法生成静态分发</summary>
    [Fact(DisplayName = "扫描控制器注册生成静态分发")]
    public void GenerateDispatchers()
    {
        var (_, result) = GeneratorTestHost.RunWithSymbols(new HttpControllerDispatchGenerator(), FullSource, ["NET5_0_OR_GREATER"]);
        var sources = GeneratorTestHost.GetGeneratedSources(result);

        var text = Assert.Single(sources).Value;
        Assert.Contains("HttpDispatchTable.RegisterController", text);

        // 泛型与 typeof 两种注册形式都生成
        Assert.Contains("typeof(global::OrderController)", text);
        Assert.Contains("typeof(global::OddController)", text);

        // 方法登记:普通、异步、值类型、带默认值、上下文注入、静态
        Assert.Contains("[\"Info\"]", text);
        Assert.Contains("[\"AsyncInfo\"]", text);
        Assert.Contains("[\"Sum\"]", text);
        Assert.Contains("[\"Echo\"]", text);
        Assert.Contains("[\"Where\"]", text);
        Assert.Contains("global::OrderController.StaticInfo()", text);

        // 参数绑定:按名取值转换、显式默认值、上下文注入、缺失必填值类型抛异常
        Assert.Contains("Reflect.ChangeType<global::System.Int32>(raw", text);
        Assert.Contains("= 7;", text);
        Assert.Contains("= context;", text);
        Assert.Contains("缺少必填参数 [a]", text);

        // 异步返回值强类型等待后写回
        Assert.Contains("await instance.AsyncInfo().ConfigureAwait(false)", text);

        // 不可静态化形态跳过:重载歧义、泛型方法、ref 参数、ValueTask、非类控制器
        Assert.DoesNotContain("[\"Run\"]", text);
        Assert.DoesNotContain("[\"Generic\"]", text);
        Assert.DoesNotContain("[\"Ref\"]", text);
        Assert.DoesNotContain("[\"Async\"]", text);
        Assert.DoesNotContain("INotController", text);
        Assert.Contains("[\"Ok\"]", text);
    }

    /// <summary>生成产物与用户代码一起编译无错误</summary>
    [Fact(DisplayName = "生成产物参与编译无错误")]
    public void GeneratedSourceCompiles()
    {
        var (compilation, _) = GeneratorTestHost.RunWithSymbols(new HttpControllerDispatchGenerator(), FullSource, ["NET5_0_OR_GREATER"]);
        var errors = compilation.GetDiagnostics().Where(e => e.Severity == DiagnosticSeverity.Error).ToList();

        Assert.True(errors.Count == 0, String.Join(Environment.NewLine, errors.Select(e => e.ToString())));
    }

    /// <summary>低版本目标(无 NET5_0_OR_GREATER 符号)不生成</summary>
    [Fact(DisplayName = "低版本目标不生成")]
    public void SkipBelowNet5()
    {
        var (_, result) = GeneratorTestHost.RunWithSymbols(new HttpControllerDispatchGenerator(), FullSource, []);
        var sources = GeneratorTestHost.GetGeneratedSources(result);

        Assert.Empty(sources);
    }

    /// <summary>未注册控制器时不产出</summary>
    [Fact(DisplayName = "无注册调用点不生成")]
    public void SkipWithoutRegistration()
    {
        var source = "using System;" + Environment.NewLine + "using NewLife.Http;" + Environment.NewLine
            + Stub + Environment.NewLine + "public class PlainController { public String Info() => \"\"; }";
        var (_, result) = GeneratorTestHost.RunWithSymbols(new HttpControllerDispatchGenerator(), source, ["NET5_0_OR_GREATER"]);
        var sources = GeneratorTestHost.GetGeneratedSources(result);

        Assert.Empty(sources);
    }
    #endregion
}