File: ParserTests.cs
Web Access
Project: src\test\Generators\Microsoft.Gen.Metrics\Unit\Microsoft.Gen.Metrics.Unit.Tests.csproj (Microsoft.Gen.Metrics.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.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Diagnostics.Metrics;
using Microsoft.Gen.Shared;
using Xunit;
 
namespace Microsoft.Gen.Metrics.Test;
 
public partial class ParserTests
{
    [Fact]
    public async Task InvalidMethodName()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""d1"")]
                static partial MetricName1 __M1(Meter meter);
            }");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorInvalidMethodName.Id, d[0].Id);
    }
 
    [Theory]
    [InlineData("Nested.MetricClassName")]
    [InlineData("Nested.Inner.MetricClassName")]
    public async Task InvalidReturnTypeLocation(string returnType)
    {
        var d = await RunGenerator(@$"
            partial class C
            {{
                [Counter(""d1"")]
                static partial {returnType} CreateMetricName(Meter meter);
            }}");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorInvalidMethodReturnTypeLocation.Id, d[0].Id);
    }
 
    [Theory]
    [InlineData("GenericMetricClass<int>")]
    [InlineData("GenericMetricClass<int, string>")]
    public async Task InvalidReturnTypeArity(string returnType)
    {
        var d = await RunGenerator(@$"
            partial class C
            {{
                [Counter(""d1"")]
                static partial {returnType} CreateMetricName(Meter meter);
            }}");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorInvalidMethodReturnTypeArity.Id, d[0].Id);
    }
 
    [Theory]
    [InlineData("void")]
    [InlineData("int")]
    [InlineData("double")]
    [InlineData("object")]
    [InlineData("CustomClass")]
    public async Task InvalidReturnType(string returnType)
    {
        var d = await RunGenerator(@$"
            partial class C
            {{
                class CustomClass {{ }}
 
                [Counter(""d1"")]
                static partial {returnType} CreateMetricName(Meter meter);
            }}");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorInvalidMethodReturnType.Id, d[0].Id);
    }
 
    [Theory]
    [InlineData("uint")]
    [InlineData("ulong")]
    [InlineData("ushort")]
    [InlineData("System.UInt16")]
    [InlineData("System.UInt32")]
    [InlineData("System.UInt64")]
    [InlineData("bool")]
    [InlineData("System.Boolean")]
    [InlineData("char")]
    [InlineData("System.Char")]
    [InlineData("CustomStruct")]
    public async Task InvalidAttributeGenericType(string genericType)
    {
        var d = await RunGenerator(@$"
            partial class C
            {{
                struct CustomStruct {{ }}
 
                [Counter<{genericType}>]
                static partial MetricsInstrument CreateMetricInstrument(Meter meter);
            }}");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorInvalidAttributeGenericType.Id, d[0].Id);
    }
 
    [Fact]
    public async Task InvalidStaticDimensionsKeyNames()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""Env&Name"")]
                static partial TestCounter CreateMetricName(Meter meter);
            }");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorInvalidTagNames.Id, d[0].Id);
    }
 
    [Fact]
    public async Task InvalidDynamicDimensionsKeyNames()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""Req*Name"")]
                static partial TestCounter CreateMetricName(Meter meter);
            }");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorInvalidTagNames.Id, d[0].Id);
    }
 
    [Fact]
    public async Task ValidDimensionsKeyNames()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""Env.Name"", ""clustr:region"", ""Req_Name"", ""Req-Status"")]
                static partial TestCounter CreateMetricName(Meter meter, string env, string region);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task ValidGenericAttribute()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter<int>(""d1"")]
                static partial TestCounter CreateTestCounter(Meter meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task NotPartialMethod()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""d1"")]
                static MetricName1 CreateMetricName(Meter meter);
            }");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorNotPartialMethod.Id, d[0].Id);
    }
 
    [Fact]
    public async Task NotStaticMethod()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""d1"")]
                partial MetricName1 CreateMetricName(Meter meter);
            }");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorNotStaticMethod.Id, d[0].Id);
    }
 
    [Fact]
    public async Task MetricNameStartingLowercaseChar()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""d1"")]
                static partial myMetric CreateMetricName(Meter meter);
            }");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorInvalidMetricName.Id, d[0].Id);
    }
 
    [Fact]
    public async Task MetricNameStartingWithNonSymbol()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""d1"")]
                static partial _Metric CreateMetricName(Meter meter);
            }");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorInvalidMetricName.Id, d[0].Id);
    }
 
    [Fact]
    public async Task MethodIsGeneric()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""d1"")]
                static partial MetricName1 CreateMetricName<T>(Meter meter);
            }");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorMethodIsGeneric.Id, d[0].Id);
    }
 
    [Fact]
    public async Task InvalidParameterName()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""d1"")]
                static partial MetricName1 CreateMetricName(Meter _meter);
            }");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorInvalidParameterName.Id, d[0].Id);
    }
 
    [Fact]
    public async Task NullDimensionNames()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(null)]
                static partial MetricName1 CreateMetricName(Meter meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task NullMetricNameParameter()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(Name = null)]
                static partial MetricName1 CreateMetricName(Meter meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task ValidParameterChecks()
    {
        var d = await RunGenerator(@"
            internal class MetricConstants
            {
                public const string Env = ""Env.Name"";
                public const string Region = ""region"";
                public const string RequestName = ""requestName"";
                public const string RequestStatus = ""requestStatus"";
            }
 
            partial class C
            {
                [Counter(MetricConstants.Env, Name = ""myMetricName"")]
                static partial MetricName1 CreateMetric(Meter meter);
 
                [Counter(MetricConstants.Env, MetricConstants.Region)]
                static partial MetricName2 CreateMetric2(Meter meter);
 
                [Counter(MetricConstants.Env, MetricConstants.Region, MetricConstants.RequestName, MetricConstants.RequestStatus)]
                static partial MetricName3 CreateMetric3(Meter meter);
 
                [Counter(MetricConstants.Env, MetricName = @""MetricType\\Standard"")]
                static partial MetricName4 CreateMetric4(Meter meter);
 
                [Counter(MetricConstants.Env, MetricName = @""MetricType\Custom"")]
                static partial MetricName5 CreateMetric5(Meter meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task ValidExtensionMethodsChecks()
    {
        var d = await RunGenerator(@"
            internal class MetricConstants
            {
                public const string Env = ""Env.Name"";
                public const string Region = ""region"";
                public const string RequestName = ""requestName"";
                public const string RequestStatus = ""requestStatus"";
            }
 
            static partial class C
            {
                [Counter(MetricConstants.Env, Name = ""myMetricName"")]
                static partial MetricName1 CreateMetric(this Meter meter);
 
                [Counter(MetricConstants.Env, MetricConstants.Region)]
                static partial MetricName2 CreateMetric2(this Meter meter);
 
                [Counter(MetricConstants.Env, MetricConstants.Region, MetricConstants.RequestName, MetricConstants.RequestStatus)]
                static partial MetricName3 CreateMetric3(this Meter meter);
 
                [Counter(MetricConstants.Env, MetricName = @""MetricType\\Standard"")]
                static partial MetricName4 CreateMetric4(this Meter meter);
 
                [Counter(MetricConstants.Env, MetricName = @""MetricType\Custom"")]
                static partial MetricName5 CreateMetric5(this Meter meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task ExistingMetricName()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""d1"")]
                static partial MetricName1 CreateMetricName(Meter meter);
 
                [Counter(""d2"")]
                static partial MetricName1 CreateMetricWithSameNameAgain(Meter meter);
            }");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorMetricNameReuse.Id, d[0].Id);
    }
 
    [Fact]
    public async Task NestedType()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                public partial class Nested
                {
                    [Counter(""d1"")]
                    static partial MetricName1 CreateMetricName(Meter meter);
                }
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task FileScopedNamespace()
    {
        var d = await RunGenerator(@"
            namespace Test;
 
            public partial class C
            {
                [Counter(""d1"")]
                static partial MetricName1 CreateMetricName(Meter meter);
            }", inNamespace: false);
 
        Assert.Empty(d);
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("string s")]
    [InlineData("int a, string s")]
    public async Task MissingMeterObject(string args)
    {
        var d = await RunGenerator(@$"
            partial class C
            {{
                [Counter(""d1"")]
                static partial MetricName1 CreateMetric({args});
            }}");
 
        var diag = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorMissingMeter.Id, diag.Id);
    }
 
    [Fact]
    public async Task MeterIsNotFirst()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""d1"")]
                static partial MetricName1 CreateMetric(string s, Meter meter);
            }");
 
        var diag = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorMissingMeter.Id, diag.Id);
    }
 
    [Fact]
    public async Task InvalidMethodBody()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""d1"")]
                static partial MetricName1 CreateMetricName(Meter meter)
                {}
            }");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorMethodHasBody.Id, d[0].Id);
    }
 
    [Fact]
    public async Task SemanticProblems()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""d1"")]
 
                [Histogram(""d1"", ""d2"")]
 
                [Gauge(""d1"", ""d2"")]
 
                [Counter(""d1"")]
                static partial 1Metric CreateMetricName(Meter meter);
 
                [CounterUnknown(""Unknown"")]
                [Counter()]
                static partial NewMetric CreateNewMetric(Meter meter);
 
                [Fact]
                static partial NewMetric1 CreateNewMetric1(Meter meter)
                {}
 
                // badly formatted
                [Counter(""d1"")]
                static partial Metric&Name1 Metric&Name1(Meter meter);
 
                // bogus parameter type
                [Counter]
                static partial Metric CreateMetric(XIMeter meter);
 
                // missing parameter name
                [Counter]
                static partial Metric2 CreateMetric2(Meter);
 
                // attribute applied to something other than method
                [Counter]
                int x;
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task MissingMeterType()
    {
        var d = await RunGenerator(@"
            namespace Microsoft.Extensions.Diagnostics.Metrics
            {
                public sealed class CounterAttribute : System.Attribute {}
                public sealed class HistogramAttribute : System.Attribute {}
            }
            partial class C
            {
                [Microsoft.Extensions.Diagnostics.Metrics.Counter]
                static partial MetricName1 CreateMetricName(Meter meter);
            }",
            wrap: false,
            inNamespace: false,
            includeBaseReferences: true,
            includeMeterReferences: false);
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task MissingCounterAttributeType()
    {
        var d = await RunGenerator(@"
            namespace System.Diagnostics.Metrics
            {
                public class Meter {}
            }
            namespace Microsoft.Extensions.Diagnostics.Metrics
            {
                public class HistogramAttribute : System.Attribute {} 
            }
            partial class C
            {
                [Microsoft.Extensions.Diagnostics.Metrics.Histogram]
                static partial MetricName1 CreateMetricName(Meter meter);
            }",
            wrap: false,
            includeBaseReferences: true,
            includeMeterReferences: false);
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task MissingHistogramAttributeType()
    {
        var d = await RunGenerator(@"
            namespace System.Diagnostics.Metrics
            {
                public class Meter {}
            }
            namespace Microsoft.Extensions.Diagnostics.Metrics
            {
                public class CounterAttribute : System.Attribute {}
            }
            partial class C
            {
                [Microsoft.Extensions.Diagnostics.Metrics.Counter]
                static partial MetricName1 CreateMetricName(Meter meter);
            }",
            wrap: false,
            includeBaseReferences: true,
            includeMeterReferences: false);
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task Cancellation()
    {
        await Assert.ThrowsAsync<OperationCanceledException>(async () =>
            _ = await RunGenerator(@"
                partial class C
                {
                    [Counter(""d1"")]
                    static partial MetricName1 CreateMetricName(Meter meter);
                }",
                cancellationToken: new CancellationToken(true)));
    }
 
    [Fact]
    public async Task ContainingClassIsInNestedNamespace()
    {
        var d = await RunGenerator(@"
            namespace Nested
            {
                partial class C
                {
                    [Counter(""d1"")]
                    static partial MetricName1 CreateMetricName(Meter meter);
                }
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task ContainingClassHasTypeParameter()
    {
        var d = await RunGenerator(@"
            partial class C<T>
            {
                [Counter(""d1"")]
                static partial MetricName1 CreateMetricName(Meter meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task MeterTypeIsConvertableToIMeter()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Counter(""d1"")]
                static partial MetricName1 CreateMetricName(Meter<string> meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task GaugeNotSupported()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Gauge(""d1"")]
                static partial NotSupportedGauge CreateGauge(Meter meter);
            }");
 
        _ = Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorGaugeNotSupported.Id, d[0].Id);
    }
 
    [Fact]
    public async Task DimensionIsDocumentedCounter()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                /// <summary>
                /// InClassDim description.
                /// </summary>
                private const string InClassDimensionName = ""InClassDim"";
                [Counter(InClassDimensionName)]
                static partial TestCounter CreateTestCounter(Meter meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task DimensionIsDocumentedHistogram()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                /// <summary>
                /// InClassDim description.
                /// </summary>
                private const string InClassDimensionName = ""InClassDim"";
                [Histogram(InClassDimensionName)]
                static partial TestHistogram CreateTestHistogram(Meter meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task CounterIsDocumented()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                /// <summary>
                /// TestCounter description.
                /// </summary>
                [Counter]
                static partial TestCounter CreateTestCounter(Meter meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task HistogramIsDocumented()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                /// <summary>
                /// TestHistogram description.
                /// </summary>
                [Histogram]
                static partial TestHistogram CreateTestHistogram(Meter meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task CounterIsNotProperlyDocumented()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                /// <summary>
                /// TestCounter description.
                /// < /summary>
                [Counter]
                static partial TestCounter CreateTestCounter(Meter meter);
            }");
 
        Assert.Single(d);
        Assert.Equal(DiagDescriptors.ErrorXmlNotLoadedCorrectly.Id, d[0].Id);
    }
 
    [Fact]
    public async Task HistogramIsNotXmlDocumented()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                /// no xml tags.
                [Histogram]
                static partial TestHistogram CreateTestHistogram(Meter meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task HistogramHasNoSummaryInXmlComment()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                /// <remarks>
                /// TestHistogram remarks.
                /// </remarks>
                [Histogram]
                static partial TestHistogram CreateTestHistogram(Meter meter);
            }");
 
        Assert.Empty(d);
    }
 
    [Fact]
    public async Task IMeterTypeParameter()
    {
        var d = await RunGenerator(@"
            partial class C
            {
                [Histogram]
                static partial TestHistogram CreateTestHistogram(IMeter meter);
            }");
 
        Assert.Empty(d);
    }
 
    private static async Task<IReadOnlyList<Diagnostic>> RunGenerator(
        string code,
        bool wrap = true,
        bool inNamespace = true,
        bool includeBaseReferences = true,
        bool includeMeterReferences = true,
        CancellationToken cancellationToken = default)
    {
        var text = code;
        if (wrap)
        {
            var nspaceStart = "namespace Test {";
            var nspaceEnd = "}";
            if (!inNamespace)
            {
                nspaceStart = "";
                nspaceEnd = "";
            }
 
            text = $@"
                    {nspaceStart}
                    using Microsoft.Extensions.Diagnostics.Metrics;
                    using System.Diagnostics.Metrics;
                    {code}
                    {nspaceEnd}
                ";
        }
 
        Assembly[]? refs = null;
        if (includeMeterReferences)
        {
            refs = new[]
            {
                Assembly.GetAssembly(typeof(Meter))!,
                Assembly.GetAssembly(typeof(CounterAttribute))!,
                Assembly.GetAssembly(typeof(HistogramAttribute))!,
                Assembly.GetAssembly(typeof(GaugeAttribute))!,
            };
        }
 
        var (d, _) = await RoslynTestUtils.RunGenerator(
            new MetricsGenerator(),
            refs,
            new[] { text },
            includeBaseReferences: includeBaseReferences,
            cancellationToken: cancellationToken).ConfigureAwait(false);
 
        return d;
    }
}