File: GeneratorTests.cs
Web Access
Project: src\test\Generators\Microsoft.Gen.MetricsReports\Unit\Microsoft.Gen.MetricsReports.Unit.Tests.csproj (Microsoft.Gen.MetricsReports.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.Diagnostics.Metrics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Extensions.Diagnostics.Metrics;
using Microsoft.Gen.Shared;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.Gen.MetricsReports.Test;
 
public class GeneratorTests(ITestOutputHelper output)
{
    private const string ReportFilename = "MetricsReport.json";
 
    [Fact]
    public void GeneratorShouldNotDoAnythingIfGeneralExecutionContextDoesNotHaveClassDeclarationSyntaxReceiver()
    {
        GeneratorExecutionContext defaultGeneralExecutionContext = default;
        new MetricsReportsGenerator().Execute(defaultGeneralExecutionContext);
 
        Assert.Null(defaultGeneralExecutionContext.SyntaxReceiver);
    }
 
    [Theory]
    [CombinatorialData]
    public async Task TestAll(bool useExplicitReportPath)
    {
        Dictionary<string, string>? options = useExplicitReportPath
            ? new() { ["build_property.MetricsReportOutputPath"] = Directory.GetCurrentDirectory() }
            : null;
 
        foreach (var inputFile in Directory.GetFiles("TestClasses"))
        {
            var stem = Path.GetFileNameWithoutExtension(inputFile);
            var goldenFileName = Path.ChangeExtension(stem, ".json");
            var goldenReportPath = Path.Combine("GoldenReports", goldenFileName);
 
            var generatedReportPath = Path.Combine(Directory.GetCurrentDirectory(), ReportFilename);
 
            if (File.Exists(goldenReportPath))
            {
                var d = await RunGenerator(await File.ReadAllTextAsync(inputFile), options);
                Assert.Empty(d);
 
                var golden = await File.ReadAllTextAsync(goldenReportPath);
                var generated = await File.ReadAllTextAsync(generatedReportPath);
 
                if (golden != generated)
                {
                    output.WriteLine($"MISMATCH: golden report {goldenReportPath}, generated {generatedReportPath}");
                    output.WriteLine("----");
                    output.WriteLine("golden:");
                    output.WriteLine(golden);
                    output.WriteLine("----");
                    output.WriteLine("generated:");
                    output.WriteLine(generated);
                    output.WriteLine("----");
                }
 
                File.Delete(generatedReportPath);
                Assert.Equal(golden, generated);
            }
            else
            {
                // generate the golden file if it doesn't already exist
                output.WriteLine($"Generating golden report: {goldenReportPath}");
                _ = await RunGenerator(await File.ReadAllTextAsync(inputFile), options, reportFileName: goldenFileName);
            }
        }
    }
 
    [Fact]
    public async Task ShouldNot_Generate_WhenDisabledViaConfig()
    {
        var inputFile = Directory.GetFiles("TestClasses").First();
        var options = new Dictionary<string, string>
        {
            ["build_property.GenerateMetricsReport"] = bool.FalseString,
            ["build_property.MetricsReportOutputPath"] = Path.GetTempPath()
        };
 
        var d = await RunGenerator(await File.ReadAllTextAsync(inputFile), options);
        Assert.Empty(d);
        Assert.False(File.Exists(Path.Combine(Path.GetTempPath(), ReportFilename)));
    }
 
    [Theory]
    [CombinatorialData]
    public async Task Should_EmitWarning_WhenPathUnavailable(bool isReportPathProvided)
    {
        var inputFile = Directory.GetFiles("TestClasses").First();
        var options = new Dictionary<string, string>
        {
            ["build_property.outputpath"] = string.Empty
        };
 
        if (isReportPathProvided)
        {
            options.Add("build_property.MetricsReportOutputPath", string.Empty);
        }
 
        var diags = await RunGenerator(await File.ReadAllTextAsync(inputFile), options);
        var diag = Assert.Single(diags);
        Assert.Equal("AUDREPGEN000", diag.Id);
        Assert.Equal(DiagnosticSeverity.Info, diag.Severity);
    }
 
    [Fact]
    public async Task Should_UseProjectDir_WhenOutputPathIsRelative()
    {
        var projectDir = Path.GetTempPath();
        var outputPath = Guid.NewGuid().ToString();
        var fullReportPath = Path.Combine(projectDir, outputPath);
        Directory.CreateDirectory(fullReportPath);
 
        try
        {
            var inputFile = Directory.GetFiles("TestClasses").First();
            var options = new Dictionary<string, string>
            {
                ["build_property.projectdir"] = projectDir,
                ["build_property.outputpath"] = outputPath
            };
 
            var diags = await RunGenerator(await File.ReadAllTextAsync(inputFile), options);
            Assert.Empty(diags);
            Assert.True(File.Exists(Path.Combine(fullReportPath, ReportFilename)));
        }
        finally
        {
            Directory.Delete(fullReportPath, recursive: true);
        }
    }
 
    private static async Task<IReadOnlyList<Diagnostic>> RunGenerator(
        string code,
        Dictionary<string, string>? analyzerOptions = null,
        CancellationToken cancellationToken = default,
        string? reportFileName = null)
    {
        Assembly[] refs =
        [
            Assembly.GetAssembly(typeof(Meter))!,
            Assembly.GetAssembly(typeof(CounterAttribute))!,
            Assembly.GetAssembly(typeof(HistogramAttribute))!,
            Assembly.GetAssembly(typeof(GaugeAttribute))!
       ];
 
        var generator = reportFileName is null
            ? new MetricsReportsGenerator()
            : new MetricsReportsGenerator(reportFileName);
 
        var (d, _) = await RoslynTestUtils.RunGenerator(
            generator,
            refs,
            new[] { code },
            new OptionsProvider(analyzerOptions),
            includeBaseReferences: true,
            cancellationToken: cancellationToken).ConfigureAwait(false);
 
        return d;
    }
 
    private sealed class Options : AnalyzerConfigOptions
    {
        private readonly Dictionary<string, string> _options;
 
        public Options(Dictionary<string, string>? analyzerOptions)
        {
            _options = analyzerOptions ?? [];
            _options.TryAdd("build_property.GenerateMetricsReport", bool.TrueString);
            _options.TryAdd("build_property.outputpath", Directory.GetCurrentDirectory());
        }
 
        public override bool TryGetValue(string key, out string value)
            => _options.TryGetValue(key, out value!);
    }
 
    private sealed class OptionsProvider(Dictionary<string, string>? analyzerOptions) : AnalyzerConfigOptionsProvider
    {
        public override AnalyzerConfigOptions GlobalOptions => new Options(analyzerOptions);
 
        public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => throw new NotSupportedException();
        public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => throw new NotSupportedException();
    }
}