File: EmitterTests.cs
Web Access
Project: src\test\Generators\Microsoft.Gen.ContextualOptions\Unit\Microsoft.Gen.ContextualOptions.Unit.Tests.csproj (Microsoft.Gen.ContextualOptions.Unit.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Extensions.Options.Contextual;
using Microsoft.Gen.ContextualOptions.Model;
using Microsoft.Gen.Shared;
using Moq;
using Xunit;
 
namespace Microsoft.Gen.ContextualOptions.Test;
 
public class EmitterTests
{
    [Fact]
    public void EmmitterDoesNotMakeStructMethodReadonly()
    {
        var delarations = SyntaxFactory
            .ParseCompilationUnit(File.ReadAllText("TestClasses/Struct1.cs"))
            .DescendantNodes()
            .OfType<TypeDeclarationSyntax>()
            .ToImmutableArray();
        var type = new OptionsContextType(
            Mock.Of<INamedTypeSymbol>(sym => sym.Name == "Struct1" && sym.ContainingNamespace.ToString() == "Microsoft.GenContextualOptions.TestClasses"),
            delarations,
            ImmutableArray.Create("Foo"));
        var generatedStruct = new Emitter().Emit(new[] { type });
        var syntaxTree = SyntaxFactory.ParseSyntaxTree(generatedStruct);
        Assert.DoesNotContain(
            syntaxTree.GetRoot().DescendantNodes().OfType<StructDeclarationSyntax>().Single().Members.Single().Modifiers,
            mod => mod.IsKind(SyntaxKind.ReadOnlyKeyword));
    }
 
    [Fact]
    public void EmmitterEmitsReceiveCallForAllProperties()
    {
        var delarations = SyntaxFactory
            .ParseCompilationUnit(File.ReadAllText("TestClasses/Class2A.cs"))
            .DescendantNodes()
            .OfType<TypeDeclarationSyntax>()
            .Concat(SyntaxFactory
                .ParseCompilationUnit(File.ReadAllText("TestClasses/Class2B.cs"))
                .DescendantNodes()
                .OfType<TypeDeclarationSyntax>())
            .ToImmutableArray();
 
        var type = new OptionsContextType(
            Mock.Of<INamedTypeSymbol>(sym => sym.Name == "Class2" && sym.ContainingNamespace.ToString() == "Microsoft.GenContextualOptions.TestClasses"),
            delarations,
            ImmutableArray.Create("Foo", "Bar"));
        var generatedStruct = new Emitter().Emit(new[] { type });
        var syntaxTree = SyntaxFactory.ParseSyntaxTree(generatedStruct);
        var statements = syntaxTree!
            .GetRoot()
            .DescendantNodes()
            .OfType<MethodDeclarationSyntax>()
            .Single()
            .Body!
            .Statements
            .Select(statement => statement.ToString());
 
        Assert.Contains("receiver.Receive(nameof(Foo), Foo);", statements);
        Assert.Contains("receiver.Receive(nameof(Bar), Bar);", statements);
    }
 
    [Fact]
    public void EmmitterEmitsValidRecord()
    {
        var delarations = SyntaxFactory
           .ParseCompilationUnit(File.ReadAllText("TestClasses/Record1.cs"))
           .DescendantNodes()
           .OfType<TypeDeclarationSyntax>()
           .ToImmutableArray();
 
        var type = new OptionsContextType(
            Mock.Of<INamedTypeSymbol>(sym => sym.Name == "Record1" && sym.ContainingNamespace.ToString() == "Microsoft.GenContextualOptions.TestClasses"),
            delarations,
            ImmutableArray.Create("Foo"));
 
        var generatedStruct = new Emitter().Emit(new[] { type });
        var syntaxTree = SyntaxFactory.ParseSyntaxTree(generatedStruct);
        var statements = syntaxTree!
            .GetRoot()
            .DescendantNodes()
            .OfType<MethodDeclarationSyntax>()
            .Single()
            .Body!
            .Statements
            .Select(statement => statement.ToString());
 
        Assert.Single(statements, "receiver.Receive(nameof(Foo), Foo);");
    }
 
    [Fact]
    public void EmmitterEmitsValidNamespacelessType()
    {
        var delarations = SyntaxFactory
           .ParseCompilationUnit(File.ReadAllText("TestClasses/NamespacelessRecord.cs"))
           .DescendantNodes()
           .OfType<TypeDeclarationSyntax>()
           .ToImmutableArray();
 
        var type = new OptionsContextType(
            Mock.Of<INamedTypeSymbol>(sym => sym.Name == "NamespacelessRecord" && sym.ContainingNamespace.IsGlobalNamespace),
            delarations,
            ImmutableArray.Create("Foo"));
 
        var generatedStruct = new Emitter().Emit(new[] { type });
        var syntaxTree = SyntaxFactory.ParseSyntaxTree(generatedStruct);
        var hasNamespace = syntaxTree!
            .GetRoot()
            .DescendantNodes()
            .OfType<NamespaceDeclarationSyntax>()
            .Any();
 
        Assert.False(hasNamespace);
    }
 
    [Fact]
    public async Task TestEmitter()
    {
        var sources = new List<string>();
        foreach (var file in Directory.GetFiles("TestClasses", "*.cs"))
        {
            sources.Add(File.ReadAllText(file));
        }
 
        var (d, r) = await RoslynTestUtils.RunGenerator(
            new ContextualOptionsGenerator(),
            new[]
            {
                typeof(OptionsContextAttribute).Assembly,
                typeof(ReadOnlySpan<>).Assembly
            },
            sources)
;
 
        Assert.Empty(d);
        _ = Assert.Single(r);
 
        var golden = File.ReadAllText($"GoldenFiles/Microsoft.Gen.ContextualOptions/Microsoft.Gen.ContextualOptions.ContextualOptionsGenerator/ContextualOptions.g.cs");
        var result = r[0].SourceText.ToString();
        Assert.Equal(golden, result);
    }
}