diff --git a/src/Generators/Microsoft.Gen.ComplianceReports/Emitter.cs b/src/Generators/Microsoft.Gen.ComplianceReports/ComplianceReportEmitter.cs similarity index 81% rename from src/Generators/Microsoft.Gen.ComplianceReports/Emitter.cs rename to src/Generators/Microsoft.Gen.ComplianceReports/ComplianceReportEmitter.cs index 2e8b63dcae7..bd891e22301 100644 --- a/src/Generators/Microsoft.Gen.ComplianceReports/Emitter.cs +++ b/src/Generators/Microsoft.Gen.ComplianceReports/ComplianceReportEmitter.cs @@ -10,22 +10,29 @@ namespace Microsoft.Gen.ComplianceReports; -internal sealed class Emitter : EmitterBase +internal sealed class ComplianceReportEmitter : JsonEmitterBase { - private readonly Stack _itemCounts = new(); - private int _itemCount; - - public Emitter() + public ComplianceReportEmitter() : base(false) { } + /// + /// Generates JSON object containing the for compliance report. + /// + /// The classified types. + /// The assembly name. + /// Whether to include the assembly name in the report. Defaulted to true. + /// The number of indentations in case its nested in other reports like .Defaulted to zero. + /// string report as json or String.Empty. [SuppressMessage("Performance", "LA0002:Use 'Microsoft.Extensions.Text.NumericExtensions.ToInvariantString' for improved performance", Justification = "Can't use that in a generator")] - public string Emit(IReadOnlyCollection classifiedTypes, string assemblyName, bool includeName = true) // show or hide assemblyName in the report,defaulted to true. + public string Emit(IReadOnlyCollection classifiedTypes, string assemblyName, + bool includeName = true, int indentationLevel = 0) // show or hide assemblyName in the report,defaulted to true. { + Indent(indentationLevel); OutObject(() => { - // this is only for not displaying a name as part of ComplianceReport properties,it should be at the root of the report, defaulted to true for beackward compatibility + // this is only for not displaying a name as part of ComplianceReport properties,it should be at the root of the report, defaulted to true for backward compatibility if (includeName) { OutNameValue("Name", assemblyName); @@ -125,65 +132,9 @@ public string Emit(IReadOnlyCollection classifiedTypes, string a } }); }); + Unindent(indentationLevel); return Capture(); } - private void NewItem() - { - if (_itemCount > 0) - { - Out(","); - } - - OutLn(); - _itemCount++; - } - - private void OutObject(Action action) - { - NewItem(); - _itemCounts.Push(_itemCount); - _itemCount = 0; - - OutIndent(); - Out("{"); - Indent(); - action(); - OutLn(); - Unindent(); - OutIndent(); - Out("}"); - - _itemCount = _itemCounts.Pop(); - } - - private void OutArray(string name, Action action) - { - NewItem(); - _itemCounts.Push(_itemCount); - _itemCount = 0; - - OutIndent(); - Out($"\"{name}\": ["); - Indent(); - action(); - OutLn(); - Unindent(); - OutIndent(); - Out("]"); - - _itemCount = _itemCounts.Pop(); - } - - private void OutNameValue(string name, string value) - { - value = value - .Replace("\\", "\\\\") - .Replace("\"", "\\\""); - - NewItem(); - OutIndent(); - Out($"\"{name}\": \"{value}\""); - } } diff --git a/src/Generators/Microsoft.Gen.ComplianceReports/ComplianceReportsGenerator.cs b/src/Generators/Microsoft.Gen.ComplianceReports/ComplianceReportsGenerator.cs index 8ff983b482c..c99455795d9 100644 --- a/src/Generators/Microsoft.Gen.ComplianceReports/ComplianceReportsGenerator.cs +++ b/src/Generators/Microsoft.Gen.ComplianceReports/ComplianceReportsGenerator.cs @@ -24,11 +24,18 @@ public sealed class ComplianceReportsGenerator : ISourceGenerator private readonly string _fileName; private string? _directory; + /// + /// Initializes a new instance of the class. + /// public ComplianceReportsGenerator() - : this(null) + : this(filePath: null) { } + /// + /// Initializes a new instance of the class. + /// + /// The report path and name. public ComplianceReportsGenerator(string? filePath) { if (filePath is not null) @@ -76,7 +83,7 @@ public void Execute(GeneratorExecutionContext context) return; } - var emitter = new Emitter(); + var emitter = new ComplianceReportEmitter(); string report = emitter.Emit(classifiedTypes, context.Compilation.AssemblyName!); context.CancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Generators/Microsoft.Gen.ComplianceReports/Microsoft.Gen.ComplianceReports.csproj b/src/Generators/Microsoft.Gen.ComplianceReports/Microsoft.Gen.ComplianceReports.csproj index 0ef9b3d55a6..d5c13b53821 100644 --- a/src/Generators/Microsoft.Gen.ComplianceReports/Microsoft.Gen.ComplianceReports.csproj +++ b/src/Generators/Microsoft.Gen.ComplianceReports/Microsoft.Gen.ComplianceReports.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Generators/Microsoft.Gen.Logging/Microsoft.Gen.Logging.csproj b/src/Generators/Microsoft.Gen.Logging/Microsoft.Gen.Logging.csproj index 38f21f0de69..d779e6fabdf 100644 --- a/src/Generators/Microsoft.Gen.Logging/Microsoft.Gen.Logging.csproj +++ b/src/Generators/Microsoft.Gen.Logging/Microsoft.Gen.Logging.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Generators/Microsoft.Gen.MetadataExtractor/MetadataEmitter.cs b/src/Generators/Microsoft.Gen.MetadataExtractor/MetadataEmitter.cs new file mode 100644 index 00000000000..db3fcd25a54 --- /dev/null +++ b/src/Generators/Microsoft.Gen.MetadataExtractor/MetadataEmitter.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.Gen.ComplianceReports; +using Microsoft.Gen.Metrics.Model; +using Microsoft.Gen.MetricsReports; +using Microsoft.Gen.Shared; + +namespace Microsoft.Gen.MetadataExtractor; +internal sealed class MetadataEmitter : JsonEmitterBase +{ + private const int IndentationLevel = 2; + private readonly MetricDefinitionEmitter _metricDefinitionEmitter; + private readonly ComplianceReportEmitter _complianceReportEmitter; + private readonly string _rootNamespace; + + public MetadataEmitter(string rootNamespace) + : base(emitPreamble: false) + { + _metricDefinitionEmitter = new MetricDefinitionEmitter(); + _complianceReportEmitter = new ComplianceReportEmitter(); + _rootNamespace = rootNamespace; + } + + [SuppressMessage("Performance", "LA0002:Use 'Microsoft.Extensions.Text.NumericExtensions.ToInvariantString' for improved performance", Justification = "Can't use that in a generator")] + public string Emit(GeneratorExecutionContext context) + { + (string metricReport, string complianceReport) metadataReport = (string.Empty, string.Empty); + + var receiver = context.SyntaxReceiver as TypeDeclarationSyntaxReceiver; + if (receiver is not null) + { + metadataReport.metricReport = HandleMetricReportGeneration(context, receiver); + metadataReport.complianceReport = HandleComplianceReportGeneration(context, receiver); + } + + OutObject(() => + { + OutNameValue("Name", context.Compilation.AssemblyName!, isSingle: true); + OutIndent(); + Out("\"ComplianceReport\": "); + Out($"{(string.IsNullOrEmpty(metadataReport.complianceReport) ? "{}" : metadataReport.complianceReport)},"); + OutLn(); + OutIndent(); + Out("\"MetricReport\": "); + Out((string.IsNullOrEmpty(metadataReport.metricReport) ? "[]" : metadataReport.metricReport)); + }); + + return Capture(); + } + + /// + /// used to generate the report for metrics annotations. + /// + /// The generator execution context. + /// The typeDeclaration syntax receiver. + /// string report as json or String.Empty. + private string HandleMetricReportGeneration(GeneratorExecutionContext context, TypeDeclarationSyntaxReceiver receiver) + { + Metrics.Parser meteringParser = new Metrics.Parser(context.Compilation, context.ReportDiagnostic, context.CancellationToken); + IReadOnlyList meteringClasses = meteringParser.GetMetricClasses(receiver.TypeDeclarations); + + if (meteringClasses.Count == 0) + { + return string.Empty; + } + + _ = context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(_rootNamespace, out var rootNamespace); + ReportedMetricClass[] reportedMetrics = MetricsReportsHelpers.MapToCommonModel(meteringClasses, rootNamespace); + return _metricDefinitionEmitter.GenerateReport(reportedMetrics, context.CancellationToken, IndentationLevel); + } + + /// + /// used to generate the report for compliance annotations. + /// + /// The generator execution context. + /// The type declaration syntax receiver. + /// string report as json or String.Empty. + private string HandleComplianceReportGeneration(GeneratorExecutionContext context, TypeDeclarationSyntaxReceiver receiver) + { + if (!SymbolLoader.TryLoad(context.Compilation, out var symbolHolder)) + { + return string.Empty; + } + + Parser parser = new Parser(context.Compilation, symbolHolder!, context.CancellationToken); + IReadOnlyList classifiedTypes = parser.GetClassifiedTypes(receiver.TypeDeclarations); + if (classifiedTypes.Count == 0) + { + // nothing to do + return string.Empty; + } + + return _complianceReportEmitter.Emit(classifiedTypes, context.Compilation.AssemblyName!, false, IndentationLevel); + } +} diff --git a/src/Generators/Microsoft.Gen.MetadataExtractor/MetadataReportsGenerator.cs b/src/Generators/Microsoft.Gen.MetadataExtractor/MetadataReportsGenerator.cs index bc0a996b103..5526e6296b3 100644 --- a/src/Generators/Microsoft.Gen.MetadataExtractor/MetadataReportsGenerator.cs +++ b/src/Generators/Microsoft.Gen.MetadataExtractor/MetadataReportsGenerator.cs @@ -5,8 +5,6 @@ using System.IO; using System.Text; using Microsoft.CodeAnalysis; -using Microsoft.Gen.ComplianceReports; -using Microsoft.Gen.MetricsReports; using Microsoft.Gen.Shared; using Microsoft.Shared.DiagnosticIds; @@ -23,22 +21,31 @@ public sealed class MetadataReportsGenerator : ISourceGenerator private const string RootNamespace = "build_property.rootnamespace"; private const string FallbackFileName = "MetadataReport.json"; private readonly string _fileName; + private string? _directory; /// /// Initializes a new instance of the class. /// public MetadataReportsGenerator() - : this(FallbackFileName) + : this(filePath: null) { } /// /// Initializes a new instance of the class. /// - /// The report file name. - public MetadataReportsGenerator(string reportFileName) + /// The report path and name. + public MetadataReportsGenerator(string? filePath) { - _fileName = reportFileName; + if (filePath is not null) + { + _directory = Path.GetDirectoryName(filePath); + _fileName = Path.GetFileName(filePath); + } + else + { + _fileName = FallbackFileName; + } } /// @@ -61,21 +68,17 @@ public void Execute(GeneratorExecutionContext context) if (context.SyntaxReceiver is not TypeDeclarationSyntaxReceiver || ((TypeDeclarationSyntaxReceiver)context.SyntaxReceiver).TypeDeclarations.Count == 0 || !GeneratorUtilities.ShouldGenerateReport(context, GenerateMetadataMSBuildProperty)) - { - return; - } - - if ((context.SyntaxReceiver is not TypeDeclarationSyntaxReceiver || ((TypeDeclarationSyntaxReceiver)context.SyntaxReceiver).TypeDeclarations.Count == 0)) { // nothing to do yet return; } var options = context.AnalyzerConfigOptions.GlobalOptions; - var path = GeneratorUtilities.TryRetrieveOptionsValue(options, ReportOutputPathMSBuildProperty, out var reportOutputPath) + _directory ??= GeneratorUtilities.TryRetrieveOptionsValue(options, ReportOutputPathMSBuildProperty, out var reportOutputPath) ? reportOutputPath! : GeneratorUtilities.GetDefaultReportOutputPath(options); - if (string.IsNullOrWhiteSpace(path)) + + if (string.IsNullOrWhiteSpace(_directory)) { // Report diagnostic: var diagnostic = new DiagnosticDescriptor( @@ -91,74 +94,13 @@ public void Execute(GeneratorExecutionContext context) return; } - (string metricReport, string complianceReport) metadataReport = (string.Empty, string.Empty); - metadataReport.metricReport = HandleMetricReportGeneration(context, (TypeDeclarationSyntaxReceiver)context.SyntaxReceiver); - metadataReport.complianceReport = HandleComplianceReportGeneration(context, (TypeDeclarationSyntaxReceiver)context.SyntaxReceiver); - - StringBuilder reportStringBuilder = new StringBuilder() - .Append("{ \"Name\": \"") - .Append(context.Compilation.AssemblyName!) - .Append("\", \"ComplianceReport\": ") - .Append((string.IsNullOrEmpty(metadataReport.complianceReport) ? "{}" : metadataReport.complianceReport)) - .Append(" ,") - .Append(" \"MetricReport\": ") - .Append((string.IsNullOrEmpty(metadataReport.metricReport) ? "[]" : metadataReport.metricReport) + " }"); + MetadataEmitter emitter = new MetadataEmitter(RootNamespace); #pragma warning disable RS1035 // Do not use APIs banned for analyzers - _ = Directory.CreateDirectory(path); + _ = Directory.CreateDirectory(_directory); - File.WriteAllText(Path.Combine(path, _fileName), reportStringBuilder.ToString(), Encoding.UTF8); + File.WriteAllText(Path.Combine(_directory, _fileName), emitter.Emit(context), Encoding.UTF8); #pragma warning restore RS1035 // Do not use APIs banned for analyzers } - - /// - /// used to generate the report for metrics annotations. - /// - /// The generator execution context. - /// The typeDeclaration syntax receiver. - /// string report as json or String.Empty. - private static string HandleMetricReportGeneration(GeneratorExecutionContext context, TypeDeclarationSyntaxReceiver receiver) - { - var meteringParser = new Metrics.Parser(context.Compilation, context.ReportDiagnostic, context.CancellationToken); - var meteringClasses = meteringParser.GetMetricClasses(receiver.TypeDeclarations); - - if (meteringClasses.Count == 0) - { - return string.Empty; - } - - _ = context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(RootNamespace, out var rootNamespace); - var reportedMetrics = MetricsReportsHelpers.MapToCommonModel(meteringClasses, rootNamespace); - var emitter = new MetricDefinitionEmitter(); - var report = emitter.GenerateReport(reportedMetrics, context.CancellationToken); - return report; - } - - /// - /// used to generate the report for compliance annotations. - /// - /// The generator execution context. - /// The type declaration syntax receiver. - /// string report as json or String.Empty. - private static string HandleComplianceReportGeneration(GeneratorExecutionContext context, TypeDeclarationSyntaxReceiver receiver) - { - if (!SymbolLoader.TryLoad(context.Compilation, out var symbolHolder)) - { - return string.Empty; - } - - var parser = new Parser(context.Compilation, symbolHolder!, context.CancellationToken); - var classifiedTypes = parser.GetClassifiedTypes(receiver.TypeDeclarations); - if (classifiedTypes.Count == 0) - { - // nothing to do - return string.Empty; - } - - var emitter = new Emitter(); - string report = emitter.Emit(classifiedTypes, context.Compilation.AssemblyName!, false); - - return report; - } } diff --git a/src/Generators/Microsoft.Gen.MetadataExtractor/Microsoft.Gen.MetadataExtractor.csproj b/src/Generators/Microsoft.Gen.MetadataExtractor/Microsoft.Gen.MetadataExtractor.csproj index 667b8383399..1567769b753 100644 --- a/src/Generators/Microsoft.Gen.MetadataExtractor/Microsoft.Gen.MetadataExtractor.csproj +++ b/src/Generators/Microsoft.Gen.MetadataExtractor/Microsoft.Gen.MetadataExtractor.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Generators/Microsoft.Gen.Metrics/Microsoft.Gen.Metrics.csproj b/src/Generators/Microsoft.Gen.Metrics/Microsoft.Gen.Metrics.csproj index e6dc5a16b79..c33b2bc82a2 100644 --- a/src/Generators/Microsoft.Gen.Metrics/Microsoft.Gen.Metrics.csproj +++ b/src/Generators/Microsoft.Gen.Metrics/Microsoft.Gen.Metrics.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Generators/Microsoft.Gen.MetricsReports/MetricDefinitionEmitter.cs b/src/Generators/Microsoft.Gen.MetricsReports/MetricDefinitionEmitter.cs index e6c23ae7f9a..1f7dd7775a9 100644 --- a/src/Generators/Microsoft.Gen.MetricsReports/MetricDefinitionEmitter.cs +++ b/src/Generators/Microsoft.Gen.MetricsReports/MetricDefinitionEmitter.cs @@ -11,69 +11,70 @@ namespace Microsoft.Gen.MetricsReports; // Stryker disable all -internal sealed class MetricDefinitionEmitter : EmitterBase +internal sealed class MetricDefinitionEmitter : JsonEmitterBase { internal MetricDefinitionEmitter() : base(false) { } - public string GenerateReport(IReadOnlyList metricClasses, CancellationToken cancellationToken) + /// + /// Generates JSON object containing the for metrics report. + /// + /// The reported metric classes. + /// The cancellation token. + /// The number of indentations in case its nested in other reports like .Defaulted to zero. + /// string report as json or String.Empty. + public string GenerateReport(IReadOnlyList metricClasses, CancellationToken cancellationToken = default, int indentationLevel = 0) { if (metricClasses == null || metricClasses.Count == 0) { return string.Empty; } - OutLn("["); - - for (int i = 0; i < metricClasses.Count; i++) + if (indentationLevel > 0) { - cancellationToken.ThrowIfCancellationRequested(); - var metricClass = metricClasses[i]; - GenMetricClassDefinition(metricClass, cancellationToken); + Indent(indentationLevel); + } - if (i < metricClasses.Count - 1) + OutArray(string.Empty, () => + { + for (int i = 0; i < metricClasses.Count; i++) { - Out(","); + cancellationToken.ThrowIfCancellationRequested(); + var metricClass = metricClasses[i]; + GenMetricClassDefinition(metricClass, cancellationToken); + if (i < metricClasses.Count - 1) + { + OutLn(","); + } } + }); - OutLn(); - } - - Out("]"); return Capture(); } private void GenMetricClassDefinition(ReportedMetricClass metricClass, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - OutLn(" {"); - - OutLn($" \"{metricClass.RootNamespace}\":"); - - if (metricClass.Methods.Length > 0) + OutObject(() => { - OutLn(" ["); - - for (int j = 0; j < metricClass.Methods.Length; j++) + OutLn(); + OutIndent(); + Out($"\"{metricClass.RootNamespace}\":"); + if (metricClass.Methods.Length > 0) { - var metricMethod = metricClass.Methods[j]; - - GenMetricMethodDefinition(metricMethod, cancellationToken); - - if (j < metricClass.Methods.Length - 1) + OutArray(string.Empty, () => { - Out(","); - } + for (int j = 0; j < metricClass.Methods.Length; j++) + { + ReportedMetricMethod metricMethod = metricClass.Methods[j]; + GenMetricMethodDefinition(metricMethod, cancellationToken); + } - OutLn(); + }, isProprietyDependent: true); } - - OutLn(" ]"); - } - - Out(" }"); + }); } private void GenMetricMethodDefinition(ReportedMetricMethod metricMethod, CancellationToken cancellationToken) @@ -87,55 +88,39 @@ private void GenMetricMethodDefinition(ReportedMetricMethod metricMethod, Cancel { cancellationToken.ThrowIfCancellationRequested(); - OutLn(" {"); - - OutLn($" \"MetricName\": \"{metricMethod.MetricName.Replace("\\", "\\\\").Replace("\"", "\\\"")}\","); - - if (!string.IsNullOrEmpty(metricMethod.Summary)) - { - OutLn($" \"MetricDescription\": \"{metricMethod.Summary.Replace("\\", "\\\\").Replace("\"", "\\\"")}\","); - } - - Out($" \"InstrumentName\": \"{metricMethod.Kind}\""); - - if (metricMethod.Dimensions.Count > 0) + OutObject(() => { - OutLn(","); + OutNameValue("MetricName", $"{metricMethod.MetricName.Replace("\\", "\\\\").Replace("\"", "\\\"")}"); - Out(" \"Dimensions\": {"); + if (!string.IsNullOrEmpty(metricMethod.Summary)) + { + OutNameValue("MetricDescription", $"{metricMethod.Summary.Replace("\\", "\\\\").Replace("\"", "\\\"")}"); + } - int k = 0; + if (metricMethod.Dimensions.Count == 0) + { + OutNameValue($"InstrumentName", $"{metricMethod.Kind}"); + } - foreach (var dimension in metricMethod.Dimensions) + if (metricMethod.Dimensions.Count > 0) { + Out(","); OutLn(); - if (metricMethod.DimensionsDescriptions.TryGetValue(dimension, out var description)) - { - Out($" \"{dimension}\": \"{description.Replace("\\", "\\\\").Replace("\"", "\\\"")}\""); - } - else + OutIndent(); + Out("\"Dimensions\":"); + OutObject(() => { - Out($" \"{dimension}\": \"\""); - } - - if (k < metricMethod.Dimensions.Count - 1) - { - Out(","); - } - - k++; + foreach (var dimension in metricMethod.Dimensions) + { + if (metricMethod.DimensionsDescriptions.TryGetValue(dimension, out var description)) + { + OutNameValue($"{dimension}", $"{description.Replace("\\", "\\\\").Replace("\"", "\\\"")}"); + } + } + }, isProprietyDependent: true); } + }); - OutLn(); - Out(" }"); - OutLn(); - } - else - { - OutLn(); - } - - Out(" }"); } catch (Exception e) { diff --git a/src/Generators/Microsoft.Gen.MetricsReports/MetricsReportsGenerator.cs b/src/Generators/Microsoft.Gen.MetricsReports/MetricsReportsGenerator.cs index 895db7ea5d5..e35291b0010 100644 --- a/src/Generators/Microsoft.Gen.MetricsReports/MetricsReportsGenerator.cs +++ b/src/Generators/Microsoft.Gen.MetricsReports/MetricsReportsGenerator.cs @@ -16,17 +16,33 @@ public class MetricsReportsGenerator : ISourceGenerator private const string GenerateMetricDefinitionReport = "build_property.GenerateMetricsReport"; private const string RootNamespace = "build_property.rootnamespace"; private const string ReportOutputPath = "build_property.MetricsReportOutputPath"; - private const string FileName = "MetricsReport.json"; + private const string FallbackFileName = "MetricsReport.json"; private readonly string _fileName; + private string? _directory; + /// + /// Initializes a new instance of the class. + /// public MetricsReportsGenerator() - : this(FileName) + : this(filePath: null) { } - internal MetricsReportsGenerator(string reportFileName) + /// + /// Initializes a new instance of the class. + /// + /// The report path and name. + internal MetricsReportsGenerator(string? filePath) { - _fileName = reportFileName; + if (filePath is not null) + { + _directory = Path.GetDirectoryName(filePath); + _fileName = Path.GetFileName(filePath); + } + else + { + _fileName = FallbackFileName; + } } public void Initialize(GeneratorInitializationContext context) @@ -45,12 +61,11 @@ public void Execute(GeneratorExecutionContext context) } var options = context.AnalyzerConfigOptions.GlobalOptions; - - var path = GeneratorUtilities.TryRetrieveOptionsValue(options, ReportOutputPath, out var reportOutputPath) + _directory ??= GeneratorUtilities.TryRetrieveOptionsValue(options, ReportOutputPath, out var reportOutputPath) ? reportOutputPath! : GeneratorUtilities.GetDefaultReportOutputPath(options); - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrWhiteSpace(_directory)) { // Report diagnostic: var diagnostic = new DiagnosticDescriptor( @@ -81,9 +96,9 @@ public void Execute(GeneratorExecutionContext context) // Suppressing until this issue is addressed in https://github.com/dotnet/extensions/issues/5390 #pragma warning disable RS1035 // Do not use APIs banned for analyzers - _ = Directory.CreateDirectory(path); + _ = Directory.CreateDirectory(_directory); - File.WriteAllText(Path.Combine(path, _fileName), report, Encoding.UTF8); + File.WriteAllText(Path.Combine(_directory, _fileName), report, Encoding.UTF8); #pragma warning restore RS1035 // Do not use APIs banned for analyzers } } diff --git a/src/Generators/Microsoft.Gen.MetricsReports/Microsoft.Gen.MetricsReports.csproj b/src/Generators/Microsoft.Gen.MetricsReports/Microsoft.Gen.MetricsReports.csproj index ecfe0d4059a..60cf58363ce 100644 --- a/src/Generators/Microsoft.Gen.MetricsReports/Microsoft.Gen.MetricsReports.csproj +++ b/src/Generators/Microsoft.Gen.MetricsReports/Microsoft.Gen.MetricsReports.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Generators/Shared/EmitterBase.cs b/src/Generators/Shared/EmitterBase.cs index c220026ad69..2d21994249c 100644 --- a/src/Generators/Shared/EmitterBase.cs +++ b/src/Generators/Shared/EmitterBase.cs @@ -13,6 +13,9 @@ namespace Microsoft.Gen.Shared; #endif internal class EmitterBase { + protected readonly Stack ItemCounts = new(); + protected int ItemCount; + private const int DefaultStringBuilderCapacity = 1024; private const int IndentChars = 4; @@ -34,16 +37,33 @@ public EmitterBase(bool emitPreamble = true) } } - protected void OutOpenBrace() + protected void OutOpenBrace(bool isRoot = false) // isRoot is used to neglect any extra indentation before the brace, root has no indentation, defaulted to false for backward compatibility. { - OutLn("{"); + if (isRoot) + { + Out("{"); + } + else + { + OutLn("{"); + } + Indent(); } - protected void OutCloseBrace() + protected void OutCloseBrace(bool isRoot = false)// isRoot is used to neglect any extra indentation before the brace, root has no indentation, defaulted to false for backward compatibility. { Unindent(); - OutLn("}"); + + if (isRoot) + { + Out("}"); + } + else + { + OutLn("}"); + } + } protected void OutCloseBraceWithExtra(string extra) @@ -98,8 +118,8 @@ protected void OutEnumeration(IEnumerable e) protected void Out(string text) => _ = _sb.Append(text); protected void Out(char ch) => _ = _sb.Append(ch); - protected void Indent() => _indent++; - protected void Unindent() => _indent--; + protected void Indent(int times = 1) => _indent += times; + protected void Unindent(int times = 1) => _indent -= times; protected void OutGeneratedCodeAttribute() => OutLn($"[{GeneratorUtilities.GeneratedCodeAttribute}]"); protected string Capture() => _sb.ToString(); } diff --git a/src/Generators/Shared/JsonEmitterBase.cs b/src/Generators/Shared/JsonEmitterBase.cs new file mode 100644 index 00000000000..d805dd7e192 --- /dev/null +++ b/src/Generators/Shared/JsonEmitterBase.cs @@ -0,0 +1,94 @@ +// 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; + +#pragma warning disable CA1716 +namespace Microsoft.Gen.Shared; +#pragma warning restore CA1716 + +#if !SHARED_PROJECT +[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] +#endif + +internal class JsonEmitterBase : EmitterBase +{ + public JsonEmitterBase(bool emitPreamble = true) + : base(emitPreamble) + { + } + + protected void OutObject(Action action, bool isProprietyDependent = false) + { + NewItem(!isProprietyDependent); + ItemCounts.Push(ItemCount); + ItemCount = 0; + + OutIndent(); + Out("{"); + Indent(); + action(); + OutLn(); + Unindent(); + OutIndent(); + Out("}"); + + ItemCount = ItemCounts.Pop(); + } + + protected void OutArray(string name, Action action, bool isProprietyDependent = false) + { + NewItem(!isProprietyDependent); + ItemCounts.Push(ItemCount); + ItemCount = 0; + + OutIndent(); + + if (string.IsNullOrEmpty(name)) + { + Out("["); + } + else + { + Out($"\"{name}\": ["); + } + + Indent(); + action(); + OutLn(); + Unindent(); + OutIndent(); + Out("]"); + + ItemCount = ItemCounts.Pop(); + } + + protected void OutNameValue(string name, string value, bool isSingle = false) + { + value = value + .Replace("\\", "\\\\") + .Replace("\"", "\\\""); + + NewItem(preAppendComma: !isSingle); + OutIndent(); + Out($"\"{name}\": \"{value}\"{(isSingle ? "," : string.Empty)}"); + + if (isSingle) + { + OutLn(); + } + } + + private void NewItem(bool preAppendComma = true) + { + if (preAppendComma && ItemCount > 0) + { + Out(","); + } + + OutLn(); + ItemCount++; + } + +} diff --git a/test/Generators/Microsoft.Gen.ComplianceReports/Unit/GeneratorTests.cs b/test/Generators/Microsoft.Gen.ComplianceReports/Unit/GeneratorTests.cs index 28dbb80a36c..2a99a5ea4fe 100644 --- a/test/Generators/Microsoft.Gen.ComplianceReports/Unit/GeneratorTests.cs +++ b/test/Generators/Microsoft.Gen.ComplianceReports/Unit/GeneratorTests.cs @@ -59,21 +59,21 @@ public async Task TestAll(bool useExplicitReportPath) foreach (var inputFile in Directory.GetFiles("TestClasses")) { - var stem = Path.GetFileNameWithoutExtension(inputFile); - var goldenReportFile = $"GoldenReports/{stem}.json"; + string stem = Path.GetFileNameWithoutExtension(inputFile); + string goldenReportFile = $"GoldenReports/{stem}.json"; if (File.Exists(goldenReportFile)) { - var tmp = Path.GetTempFileName(); - var d = await RunGenerator(File.ReadAllText(inputFile), tmp, options); - Assert.Empty(d); + string temporaryReportFile = Path.GetTempFileName(); + var diagnostic = await RunGenerator(File.ReadAllText(inputFile), temporaryReportFile, options); + Assert.Empty(diagnostic); - var golden = File.ReadAllText(goldenReportFile); - var generated = File.ReadAllText(tmp); + string golden = File.ReadAllText(goldenReportFile); + string generated = File.ReadAllText(temporaryReportFile); if (golden != generated) { - _output.WriteLine($"MISMATCH: golden report {goldenReportFile}, generated {tmp}"); + _output.WriteLine($"MISMATCH: golden report {goldenReportFile}, generated {temporaryReportFile}"); _output.WriteLine("----"); _output.WriteLine("golden:"); _output.WriteLine(golden); @@ -83,7 +83,7 @@ public async Task TestAll(bool useExplicitReportPath) _output.WriteLine("----"); } - File.Delete(tmp); + File.Delete(temporaryReportFile); Assert.Equal(golden, generated); } else diff --git a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/Basic.json b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/Basic.json index 2e9867dc658..fdbddc7eab1 100644 --- a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/Basic.json +++ b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/Basic.json @@ -1,118 +1,123 @@ -{ "Name": "test.dll", "ComplianceReport": + { - "Types": [ + "Name": "test.dll", + "ComplianceReport": { - "Name": "Test.Basic", - "Members": [ + "Types": [ { - "Name": "F0", - "Type": "int", - "File": "src-0.cs", - "Line": "18", - "Classifications": [ + "Name": "Test.Basic", + "Members": [ { - "Name": "C1" - }, - { - "Name": "C2", - "Notes": "Note 1" - } - ] - }, - { - "Name": "F1", - "Type": "int", - "File": "src-0.cs", - "Line": "21", - "Classifications": [ - { - "Name": "C1" - }, - { - "Name": "C2" - } - ] - }, - { - "Name": "P0", - "Type": "int", - "File": "src-0.cs", - "Line": "11", - "Classifications": [ - { - "Name": "C1" - }, - { - "Name": "C3", - "Notes": "Note 2" - }, - { - "Name": "C4" - } - ] - }, - { - "Name": "P1", - "Type": "int", - "File": "src-0.cs", - "Line": "27", - "Classifications": [ - { - "Name": "C1" - }, - { - "Name": "C3" - } - ] - } - ], - "Logging Methods": [ - { - "Name": "LogHello", - "Parameters": [ - { - "Name": "user", - "Type": "string", + "Name": "F0", + "Type": "int", "File": "src-0.cs", - "Line": "30", + "Line": "18", "Classifications": [ + { + "Name": "C1" + }, { "Name": "C2", - "Notes": "Note 3" + "Notes": "Note 1" } ] }, { - "Name": "port", + "Name": "F1", "Type": "int", "File": "src-0.cs", - "Line": "30" - } - ] - }, - { - "Name": "LogWorld", - "Parameters": [ + "Line": "21", + "Classifications": [ + { + "Name": "C1" + }, + { + "Name": "C2" + } + ] + }, { - "Name": "user", - "Type": "string", + "Name": "P0", + "Type": "int", "File": "src-0.cs", - "Line": "33", + "Line": "11", "Classifications": [ { - "Name": "C2" + "Name": "C1" + }, + { + "Name": "C3", + "Notes": "Note 2" + }, + { + "Name": "C4" } ] }, { - "Name": "port", + "Name": "P1", "Type": "int", "File": "src-0.cs", - "Line": "33" + "Line": "27", + "Classifications": [ + { + "Name": "C1" + }, + { + "Name": "C3" + } + ] + } + ], + "Logging Methods": [ + { + "Name": "LogHello", + "Parameters": [ + { + "Name": "user", + "Type": "string", + "File": "src-0.cs", + "Line": "30", + "Classifications": [ + { + "Name": "C2", + "Notes": "Note 3" + } + ] + }, + { + "Name": "port", + "Type": "int", + "File": "src-0.cs", + "Line": "30" + } + ] + }, + { + "Name": "LogWorld", + "Parameters": [ + { + "Name": "user", + "Type": "string", + "File": "src-0.cs", + "Line": "33", + "Classifications": [ + { + "Name": "C2" + } + ] + }, + { + "Name": "port", + "Type": "int", + "File": "src-0.cs", + "Line": "33" + } + ] } ] } ] - } - ] -} , "MetricReport": [] } \ No newline at end of file + }, + "MetricReport": [] +} \ No newline at end of file diff --git a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/Inheritance.json b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/Inheritance.json index 084f52598d5..dd524d8ba03 100644 --- a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/Inheritance.json +++ b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/Inheritance.json @@ -1,62 +1,67 @@ -{ "Name": "test.dll", "ComplianceReport": + { - "Types": [ + "Name": "test.dll", + "ComplianceReport": { - "Name": "Test.Base", - "Members": [ + "Types": [ { - "Name": "P0", - "Type": "int", - "File": "src-0.cs", - "Line": "11", - "Classifications": [ + "Name": "Test.Base", + "Members": [ { - "Name": "C1" - } - ] - }, - { - "Name": "P1", - "Type": "int", - "File": "src-0.cs", - "Line": "14", - "Classifications": [ - { - "Name": "C2" - } - ] - } - ] - }, - { - "Name": "Test.Inherited", - "Members": [ - { - "Name": "P0", - "Type": "int", - "File": "src-0.cs", - "Line": "11", - "Classifications": [ + "Name": "P0", + "Type": "int", + "File": "src-0.cs", + "Line": "11", + "Classifications": [ + { + "Name": "C1" + } + ] + }, { - "Name": "C1" + "Name": "P1", + "Type": "int", + "File": "src-0.cs", + "Line": "14", + "Classifications": [ + { + "Name": "C2" + } + ] } ] }, { - "Name": "P1", - "Type": "int", - "File": "src-0.cs", - "Line": "14", - "Classifications": [ + "Name": "Test.Inherited", + "Members": [ { - "Name": "C2" + "Name": "P0", + "Type": "int", + "File": "src-0.cs", + "Line": "11", + "Classifications": [ + { + "Name": "C1" + } + ] }, { - "Name": "C3" + "Name": "P1", + "Type": "int", + "File": "src-0.cs", + "Line": "14", + "Classifications": [ + { + "Name": "C2" + }, + { + "Name": "C3" + } + ] } ] } ] - } - ] -} , "MetricReport": [] } \ No newline at end of file + }, + "MetricReport": [] +} \ No newline at end of file diff --git a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/LogMethod.json b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/LogMethod.json index 8f14aa9c92f..46b49b8569f 100644 --- a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/LogMethod.json +++ b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/LogMethod.json @@ -1,33 +1,38 @@ -{ "Name": "test.dll", "ComplianceReport": + { - "Types": [ + "Name": "test.dll", + "ComplianceReport": { - "Name": "Test.LogMethod", - "Logging Methods": [ + "Types": [ { - "Name": "LogHello", - "Parameters": [ + "Name": "Test.LogMethod", + "Logging Methods": [ { - "Name": "user", - "Type": "string", - "File": "src-0.cs", - "Line": "11", - "Classifications": [ + "Name": "LogHello", + "Parameters": [ { - "Name": "C2", - "Notes": "Note 3" + "Name": "user", + "Type": "string", + "File": "src-0.cs", + "Line": "11", + "Classifications": [ + { + "Name": "C2", + "Notes": "Note 3" + } + ] + }, + { + "Name": "port", + "Type": "int", + "File": "src-0.cs", + "Line": "11" } ] - }, - { - "Name": "port", - "Type": "int", - "File": "src-0.cs", - "Line": "11" } ] } ] - } - ] -} , "MetricReport": [] } \ No newline at end of file + }, + "MetricReport": [] +} \ No newline at end of file diff --git a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterAttributedWithXmlDescriptions.json b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterAttributedWithXmlDescriptions.json index c89592c2bf7..e0953990578 100644 --- a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterAttributedWithXmlDescriptions.json +++ b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterAttributedWithXmlDescriptions.json @@ -1,31 +1,37 @@ -{ "Name": "test.dll", "ComplianceReport": {} , "MetricReport": [ - { - "TestClasses": - [ - { - "MetricName": "CounterWithDescription", - "MetricDescription": "CounterWithDescription description.", - "InstrumentName": "Counter" - }, - { - "MetricName": "HistogramWithDescription", - "MetricDescription": "HistogramWithDescription description.", - "InstrumentName": "Histogram" - }, - { - "MetricName": "HistogramWithWrongDescription", - "MetricDescription": "(Missing Summary)", - "InstrumentName": "Histogram" - }, - { - "MetricName": "ConstDescribedCounter", - "MetricDescription": "CreateConstDescribedCounter description.", - "InstrumentName": "Counter", - "Dimensions": { - "Dim4": "Dim4 description.", - "InClassDim": "InClassDim description." - } - } - ] - } -] } \ No newline at end of file + +{ + "Name": "test.dll", + "ComplianceReport": {}, + "MetricReport": + [ + { + "TestClasses": + [ + { + "MetricName": "CounterWithDescription", + "MetricDescription": "CounterWithDescription description.", + "InstrumentName": "Counter" + }, + { + "MetricName": "HistogramWithDescription", + "MetricDescription": "HistogramWithDescription description.", + "InstrumentName": "Histogram" + }, + { + "MetricName": "HistogramWithWrongDescription", + "MetricDescription": "(Missing Summary)", + "InstrumentName": "Histogram" + }, + { + "MetricName": "ConstDescribedCounter", + "MetricDescription": "CreateConstDescribedCounter description.", + "Dimensions": + { + "Dim4": "Dim4 description.", + "InClassDim": "InClassDim description." + } + } + ] + } + ] +} \ No newline at end of file diff --git a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterAttributedWithXmlDescriptions_RecordProperty.json b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterAttributedWithXmlDescriptions_RecordProperty.json index 270a4b1d979..142b6dcf637 100644 --- a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterAttributedWithXmlDescriptions_RecordProperty.json +++ b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterAttributedWithXmlDescriptions_RecordProperty.json @@ -1,230 +1,236 @@ -{ "Name": "test.dll", "ComplianceReport": + { - "Types": [ + "Name": "test.dll", + "ComplianceReport": { - "Name": "Test.DerivedRecordProperty", - "Members": [ + "Types": [ { - "Name": "EqualityContract", - "Type": "System.Type", - "File": "src-0.cs", - "Line": "18", - "Classifications": [ + "Name": "Test.DerivedRecordProperty", + "Members": [ { - "Name": "C1" - } - ] - }, - { - "Name": "F3", - "Type": "int", - "File": "src-0.cs", - "Line": "21", - "Classifications": [ - { - "Name": "C2", - "Notes": "Note 1" - } - ] - }, - { - "Name": "F4", - "Type": "int", - "File": "src-0.cs", - "Line": "24", - "Classifications": [ - { - "Name": "C2" - } - ] - }, - { - "Name": "P0", - "Type": "int", - "File": "src-0.cs", - "Line": "27", - "Classifications": [ - { - "Name": "C1" + "Name": "EqualityContract", + "Type": "System.Type", + "File": "src-0.cs", + "Line": "18", + "Classifications": [ + { + "Name": "C1" + } + ] }, { - "Name": "C2", - "Notes": "Note 2" + "Name": "F3", + "Type": "int", + "File": "src-0.cs", + "Line": "21", + "Classifications": [ + { + "Name": "C2", + "Notes": "Note 1" + } + ] }, { - "Name": "C3", - "Notes": "Note 3" + "Name": "F4", + "Type": "int", + "File": "src-0.cs", + "Line": "24", + "Classifications": [ + { + "Name": "C2" + } + ] }, { - "Name": "C4" - } - ] - }, - { - "Name": "P1", - "Type": "int", - "File": "src-0.cs", - "Line": "30", - "Classifications": [ - { - "Name": "C3" - } - ] - } - ] - }, - { - "Name": "Test.RecordProperty", - "Members": [ - { - "Name": "F0", - "Type": "string", - "File": "src-0.cs", - "Line": "18", - "Classifications": [ - { - "Name": "C2" - } - ] - }, - { - "Name": "F2", - "Type": "int", - "File": "src-0.cs", - "Line": "18", - "Classifications": [ - { - "Name": "C3" - } - ] - }, - { - "Name": "F3", - "Type": "int", - "File": "src-0.cs", - "Line": "21", - "Classifications": [ + "Name": "P0", + "Type": "int", + "File": "src-0.cs", + "Line": "27", + "Classifications": [ + { + "Name": "C1" + }, + { + "Name": "C2", + "Notes": "Note 2" + }, + { + "Name": "C3", + "Notes": "Note 3" + }, + { + "Name": "C4" + } + ] + }, { - "Name": "C2", - "Notes": "Note 1" + "Name": "P1", + "Type": "int", + "File": "src-0.cs", + "Line": "30", + "Classifications": [ + { + "Name": "C3" + } + ] } ] }, { - "Name": "F4", - "Type": "int", - "File": "src-0.cs", - "Line": "24", - "Classifications": [ + "Name": "Test.RecordProperty", + "Members": [ { - "Name": "C2" - } - ] - }, - { - "Name": "P0", - "Type": "int", - "File": "src-0.cs", - "Line": "15", - "Classifications": [ + "Name": "F0", + "Type": "string", + "File": "src-0.cs", + "Line": "18", + "Classifications": [ + { + "Name": "C2" + } + ] + }, { - "Name": "C3", - "Notes": "Note 3" + "Name": "F2", + "Type": "int", + "File": "src-0.cs", + "Line": "18", + "Classifications": [ + { + "Name": "C3" + } + ] }, { - "Name": "C4" - } - ] - }, - { - "Name": "P1", - "Type": "int", - "File": "src-0.cs", - "Line": "30", - "Classifications": [ + "Name": "F3", + "Type": "int", + "File": "src-0.cs", + "Line": "21", + "Classifications": [ + { + "Name": "C2", + "Notes": "Note 1" + } + ] + }, { - "Name": "C3" - } - ] - } - ], - "Logging Methods": [ - { - "Name": "LogHello", - "Parameters": [ + "Name": "F4", + "Type": "int", + "File": "src-0.cs", + "Line": "24", + "Classifications": [ + { + "Name": "C2" + } + ] + }, { - "Name": "user", - "Type": "string", + "Name": "P0", + "Type": "int", "File": "src-0.cs", - "Line": "33", + "Line": "15", "Classifications": [ { "Name": "C3", "Notes": "Note 3" + }, + { + "Name": "C4" } ] }, { - "Name": "port", + "Name": "P1", "Type": "int", "File": "src-0.cs", - "Line": "33" + "Line": "30", + "Classifications": [ + { + "Name": "C3" + } + ] } - ] - }, - { - "Name": "LogWorld", - "Parameters": [ + ], + "Logging Methods": [ { - "Name": "user", - "Type": "string", - "File": "src-0.cs", - "Line": "36", - "Classifications": [ + "Name": "LogHello", + "Parameters": [ { - "Name": "C2" + "Name": "user", + "Type": "string", + "File": "src-0.cs", + "Line": "33", + "Classifications": [ + { + "Name": "C3", + "Notes": "Note 3" + } + ] + }, + { + "Name": "port", + "Type": "int", + "File": "src-0.cs", + "Line": "33" } ] }, { - "Name": "port", - "Type": "int", - "File": "src-0.cs", - "Line": "36" + "Name": "LogWorld", + "Parameters": [ + { + "Name": "user", + "Type": "string", + "File": "src-0.cs", + "Line": "36", + "Classifications": [ + { + "Name": "C2" + } + ] + }, + { + "Name": "port", + "Type": "int", + "File": "src-0.cs", + "Line": "36" + } + ] } ] } ] - } - ] -} , "MetricReport": [ - { - "TestClasses": - [ - { - "MetricName": "CounterWithDescription", - "MetricDescription": "CounterWithDescription description.", - "InstrumentName": "Counter" - }, - { - "MetricName": "HistogramWithDescription", - "MetricDescription": "HistogramWithDescription description.", - "InstrumentName": "Histogram" - }, - { - "MetricName": "HistogramWithWrongDescription", - "MetricDescription": "(Missing Summary)", - "InstrumentName": "Histogram" - }, - { - "MetricName": "ConstDescribedCounter", - "MetricDescription": "CreateConstDescribedCounter description.", - "InstrumentName": "Counter", - "Dimensions": { - "Dim4": "Dim4 description.", - "InClassDim": "InClassDim description." - } - } - ] - } -] } \ No newline at end of file + }, + "MetricReport": + [ + { + "TestClasses": + [ + { + "MetricName": "CounterWithDescription", + "MetricDescription": "CounterWithDescription description.", + "InstrumentName": "Counter" + }, + { + "MetricName": "HistogramWithDescription", + "MetricDescription": "HistogramWithDescription description.", + "InstrumentName": "Histogram" + }, + { + "MetricName": "HistogramWithWrongDescription", + "MetricDescription": "(Missing Summary)", + "InstrumentName": "Histogram" + }, + { + "MetricName": "ConstDescribedCounter", + "MetricDescription": "CreateConstDescribedCounter description.", + "Dimensions": + { + "Dim4": "Dim4 description.", + "InClassDim": "InClassDim description." + } + } + ] + } + ] +} \ No newline at end of file diff --git a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterDimensionsAttributedWithXmlDescriptions.json b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterDimensionsAttributedWithXmlDescriptions.json index 3c58e7c0670..d6228d74199 100644 --- a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterDimensionsAttributedWithXmlDescriptions.json +++ b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterDimensionsAttributedWithXmlDescriptions.json @@ -1,39 +1,44 @@ -{ "Name": "test.dll", "ComplianceReport": {} , "MetricReport": [ - { - "TestClasses": - [ - { - "MetricName": "DescribedDimensionCounter", - "MetricDescription": "(Missing Summary)", - "InstrumentName": "Counter", - "Dimensions": { - "Dimension1": "Dimension1 description.", - "Dim1": "" - } - }, - { - "MetricName": "DescribedDimensionHistogram", - "MetricDescription": "(Missing Summary)", - "InstrumentName": "Histogram", - "Dimensions": { - "Dimension2": "Dimension2 description.", - "DimensionDefinedInMetricClass": "DimensionDefinedInMetricClass description." - } - }, - { - "MetricName": "MyStrongTypeMetricWithDescription", - "MetricDescription": "(Missing Summary)", - "InstrumentName": "Counter", - "Dimensions": { - "AnotherDimension": "Gets or sets anotherDimension.", - "MetricEnum": "Gets or sets MetricEnum.", - "Enum2": "Gets or sets MetricEnum2.", - "Dim2": "Gets or sets Dim2.", - "dim2FromAttribute": "Gets or sets SomeDim.", - "Dim4Struct": "Gets or sets Dim4Struct.", - "Dim5FromAttribute": "Gets or sets Dim5Struct." - } - } - ] - } -] } \ No newline at end of file + +{ + "Name": "test.dll", + "ComplianceReport": {}, + "MetricReport": + [ + { + "TestClasses": + [ + { + "MetricName": "DescribedDimensionCounter", + "MetricDescription": "(Missing Summary)", + "Dimensions": + { + "Dimension1": "Dimension1 description." + } + }, + { + "MetricName": "DescribedDimensionHistogram", + "MetricDescription": "(Missing Summary)", + "Dimensions": + { + "Dimension2": "Dimension2 description.", + "DimensionDefinedInMetricClass": "DimensionDefinedInMetricClass description." + } + }, + { + "MetricName": "MyStrongTypeMetricWithDescription", + "MetricDescription": "(Missing Summary)", + "Dimensions": + { + "AnotherDimension": "Gets or sets anotherDimension.", + "MetricEnum": "Gets or sets MetricEnum.", + "Enum2": "Gets or sets MetricEnum2.", + "Dim2": "Gets or sets Dim2.", + "dim2FromAttribute": "Gets or sets SomeDim.", + "Dim4Struct": "Gets or sets Dim4Struct.", + "Dim5FromAttribute": "Gets or sets Dim5Struct." + } + } + ] + } + ] +} \ No newline at end of file diff --git a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterDimensionsAttributedWithXmlDescriptions_LogMethod.json b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterDimensionsAttributedWithXmlDescriptions_LogMethod.json index 021cf13caa6..286b093ee53 100644 --- a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterDimensionsAttributedWithXmlDescriptions_LogMethod.json +++ b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/MeterDimensionsAttributedWithXmlDescriptions_LogMethod.json @@ -1,71 +1,76 @@ -{ "Name": "test.dll", "ComplianceReport": + { - "Types": [ + "Name": "test.dll", + "ComplianceReport": { - "Name": "Test.LogMethod", - "Logging Methods": [ + "Types": [ { - "Name": "LogHello", - "Parameters": [ + "Name": "Test.LogMethod", + "Logging Methods": [ { - "Name": "user", - "Type": "string", - "File": "src-0.cs", - "Line": "14", - "Classifications": [ + "Name": "LogHello", + "Parameters": [ { - "Name": "C2", - "Notes": "Note 3" + "Name": "user", + "Type": "string", + "File": "src-0.cs", + "Line": "14", + "Classifications": [ + { + "Name": "C2", + "Notes": "Note 3" + } + ] + }, + { + "Name": "port", + "Type": "int", + "File": "src-0.cs", + "Line": "14" } ] - }, - { - "Name": "port", - "Type": "int", - "File": "src-0.cs", - "Line": "14" } ] } ] - } - ] -} , "MetricReport": [ - { - "TestClasses": - [ - { - "MetricName": "DescribedDimensionCounter", - "MetricDescription": "(Missing Summary)", - "InstrumentName": "Counter", - "Dimensions": { - "Dimension1": "Dimension1 description.", - "Dim1": "" - } - }, - { - "MetricName": "DescribedDimensionHistogram", - "MetricDescription": "(Missing Summary)", - "InstrumentName": "Histogram", - "Dimensions": { - "Dimension2": "Dimension2 description.", - "DimensionDefinedInMetricClass": "DimensionDefinedInMetricClass description." - } - }, - { - "MetricName": "MyStrongTypeMetricWithDescription", - "MetricDescription": "(Missing Summary)", - "InstrumentName": "Counter", - "Dimensions": { - "AnotherDimension": "Gets or sets anotherDimension.", - "MetricEnum": "Gets or sets MetricEnum.", - "MetricEnum2": "Gets or sets MetricEnum2.", - "Dim2": "Gets or sets Dim2.", - "SomeDim": "Gets or sets SomeDim.", - "Dim4Struct": "Gets or sets Dim4Struct.", - "Dim5Struct": "Gets or sets Dim5Struct." - } - } - ] - } -] } \ No newline at end of file + }, + "MetricReport": + [ + { + "TestClasses": + [ + { + "MetricName": "DescribedDimensionCounter", + "MetricDescription": "(Missing Summary)", + "Dimensions": + { + "Dimension1": "Dimension1 description." + } + }, + { + "MetricName": "DescribedDimensionHistogram", + "MetricDescription": "(Missing Summary)", + "Dimensions": + { + "Dimension2": "Dimension2 description.", + "DimensionDefinedInMetricClass": "DimensionDefinedInMetricClass description." + } + }, + { + "MetricName": "MyStrongTypeMetricWithDescription", + "MetricDescription": "(Missing Summary)", + "Dimensions": + { + "AnotherDimension": "Gets or sets anotherDimension.", + "MetricEnum": "Gets or sets MetricEnum.", + "MetricEnum2": "Gets or sets MetricEnum2.", + "Dim2": "Gets or sets Dim2.", + "SomeDim": "Gets or sets SomeDim.", + "Dim4Struct": "Gets or sets Dim4Struct.", + "Dim5Struct": "Gets or sets Dim5Struct." + } + } + ] + } + ] +} \ No newline at end of file diff --git a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/RecordProperty.json b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/RecordProperty.json index 7e78ce5d2ad..00db417a23b 100644 --- a/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/RecordProperty.json +++ b/test/Generators/Microsoft.Gen.MetadataExtractor/GoldenReports/RecordProperty.json @@ -1,200 +1,205 @@ -{ "Name": "test.dll", "ComplianceReport": + { - "Types": [ + "Name": "test.dll", + "ComplianceReport": { - "Name": "Test.DerivedRecordProperty", - "Members": [ + "Types": [ { - "Name": "EqualityContract", - "Type": "System.Type", - "File": "src-0.cs", - "Line": "14", - "Classifications": [ + "Name": "Test.DerivedRecordProperty", + "Members": [ { - "Name": "C1" - } - ] - }, - { - "Name": "F3", - "Type": "int", - "File": "src-0.cs", - "Line": "17", - "Classifications": [ - { - "Name": "C2", - "Notes": "Note 1" - } - ] - }, - { - "Name": "F4", - "Type": "int", - "File": "src-0.cs", - "Line": "20", - "Classifications": [ - { - "Name": "C2" - } - ] - }, - { - "Name": "P0", - "Type": "int", - "File": "src-0.cs", - "Line": "23", - "Classifications": [ - { - "Name": "C1" + "Name": "EqualityContract", + "Type": "System.Type", + "File": "src-0.cs", + "Line": "14", + "Classifications": [ + { + "Name": "C1" + } + ] }, { - "Name": "C2", - "Notes": "Note 2" + "Name": "F3", + "Type": "int", + "File": "src-0.cs", + "Line": "17", + "Classifications": [ + { + "Name": "C2", + "Notes": "Note 1" + } + ] }, { - "Name": "C3", - "Notes": "Note 3" + "Name": "F4", + "Type": "int", + "File": "src-0.cs", + "Line": "20", + "Classifications": [ + { + "Name": "C2" + } + ] }, { - "Name": "C4" - } - ] - }, - { - "Name": "P1", - "Type": "int", - "File": "src-0.cs", - "Line": "26", - "Classifications": [ - { - "Name": "C3" - } - ] - } - ] - }, - { - "Name": "Test.RecordProperty", - "Members": [ - { - "Name": "F0", - "Type": "string", - "File": "src-0.cs", - "Line": "14", - "Classifications": [ - { - "Name": "C2" - } - ] - }, - { - "Name": "F2", - "Type": "int", - "File": "src-0.cs", - "Line": "14", - "Classifications": [ - { - "Name": "C3" - } - ] - }, - { - "Name": "F3", - "Type": "int", - "File": "src-0.cs", - "Line": "17", - "Classifications": [ + "Name": "P0", + "Type": "int", + "File": "src-0.cs", + "Line": "23", + "Classifications": [ + { + "Name": "C1" + }, + { + "Name": "C2", + "Notes": "Note 2" + }, + { + "Name": "C3", + "Notes": "Note 3" + }, + { + "Name": "C4" + } + ] + }, { - "Name": "C2", - "Notes": "Note 1" + "Name": "P1", + "Type": "int", + "File": "src-0.cs", + "Line": "26", + "Classifications": [ + { + "Name": "C3" + } + ] } ] }, { - "Name": "F4", - "Type": "int", - "File": "src-0.cs", - "Line": "20", - "Classifications": [ + "Name": "Test.RecordProperty", + "Members": [ { - "Name": "C2" - } - ] - }, - { - "Name": "P0", - "Type": "int", - "File": "src-0.cs", - "Line": "11", - "Classifications": [ + "Name": "F0", + "Type": "string", + "File": "src-0.cs", + "Line": "14", + "Classifications": [ + { + "Name": "C2" + } + ] + }, { - "Name": "C3", - "Notes": "Note 3" + "Name": "F2", + "Type": "int", + "File": "src-0.cs", + "Line": "14", + "Classifications": [ + { + "Name": "C3" + } + ] }, { - "Name": "C4" - } - ] - }, - { - "Name": "P1", - "Type": "int", - "File": "src-0.cs", - "Line": "26", - "Classifications": [ + "Name": "F3", + "Type": "int", + "File": "src-0.cs", + "Line": "17", + "Classifications": [ + { + "Name": "C2", + "Notes": "Note 1" + } + ] + }, { - "Name": "C3" - } - ] - } - ], - "Logging Methods": [ - { - "Name": "LogHello", - "Parameters": [ + "Name": "F4", + "Type": "int", + "File": "src-0.cs", + "Line": "20", + "Classifications": [ + { + "Name": "C2" + } + ] + }, { - "Name": "user", - "Type": "string", + "Name": "P0", + "Type": "int", "File": "src-0.cs", - "Line": "29", + "Line": "11", "Classifications": [ { "Name": "C3", "Notes": "Note 3" + }, + { + "Name": "C4" } ] }, { - "Name": "port", + "Name": "P1", "Type": "int", "File": "src-0.cs", - "Line": "29" + "Line": "26", + "Classifications": [ + { + "Name": "C3" + } + ] } - ] - }, - { - "Name": "LogWorld", - "Parameters": [ + ], + "Logging Methods": [ { - "Name": "user", - "Type": "string", - "File": "src-0.cs", - "Line": "32", - "Classifications": [ + "Name": "LogHello", + "Parameters": [ { - "Name": "C2" + "Name": "user", + "Type": "string", + "File": "src-0.cs", + "Line": "29", + "Classifications": [ + { + "Name": "C3", + "Notes": "Note 3" + } + ] + }, + { + "Name": "port", + "Type": "int", + "File": "src-0.cs", + "Line": "29" } ] }, { - "Name": "port", - "Type": "int", - "File": "src-0.cs", - "Line": "32" + "Name": "LogWorld", + "Parameters": [ + { + "Name": "user", + "Type": "string", + "File": "src-0.cs", + "Line": "32", + "Classifications": [ + { + "Name": "C2" + } + ] + }, + { + "Name": "port", + "Type": "int", + "File": "src-0.cs", + "Line": "32" + } + ] } ] } ] - } - ] -} , "MetricReport": [] } \ No newline at end of file + }, + "MetricReport": [] +} \ No newline at end of file diff --git a/test/Generators/Microsoft.Gen.MetadataExtractor/Unit/GeneratorTests.cs b/test/Generators/Microsoft.Gen.MetadataExtractor/Unit/GeneratorTests.cs index 6d7f9a23c61..96227e493e6 100644 --- a/test/Generators/Microsoft.Gen.MetadataExtractor/Unit/GeneratorTests.cs +++ b/test/Generators/Microsoft.Gen.MetadataExtractor/Unit/GeneratorTests.cs @@ -74,25 +74,24 @@ public async Task TestAll(bool useExplicitReportPath) ? new() { ["build_property.MetadataReportOutputPath"] = Directory.GetCurrentDirectory() } : null; - foreach (var inputFile in Directory.GetFiles("TestClasses")) + foreach (string 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); + string stem = Path.GetFileNameWithoutExtension(inputFile); + string goldenFileName = Path.ChangeExtension(stem, ".json"); + string goldenReportPath = Path.Combine("GoldenReports", goldenFileName); if (File.Exists(goldenReportPath)) { - var d = await RunGenerator(await File.ReadAllTextAsync(inputFile), options); - Assert.Empty(d); + string temporaryReportFile = Path.GetTempFileName(); + var diagnostic = await RunGenerator(await File.ReadAllTextAsync(inputFile), temporaryReportFile, options: options); + Assert.Empty(diagnostic); var golden = await File.ReadAllTextAsync(goldenReportPath); - var generated = await File.ReadAllTextAsync(generatedReportPath); + var generated = await File.ReadAllTextAsync(temporaryReportFile); if (golden != generated) { - output.WriteLine($"MISMATCH: golden report {goldenReportPath}, generated {generatedReportPath}"); + output.WriteLine($"MISMATCH: golden report {goldenReportPath}, generated {temporaryReportFile}"); output.WriteLine("----"); output.WriteLine("golden:"); output.WriteLine(golden); @@ -102,15 +101,15 @@ public async Task TestAll(bool useExplicitReportPath) output.WriteLine("----"); } - File.Delete(generatedReportPath); + File.Delete(temporaryReportFile); - Assert.Equal(golden, generated); + Assert.Equal(NormalizeEscapes(golden), NormalizeEscapes(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); + _ = await RunGenerator(await File.ReadAllTextAsync(inputFile), goldenFileName, options); } } } @@ -129,7 +128,7 @@ public async Task ShouldNot_Generate_WhenDisabledViaConfig() ["build_property.rootnamespace"] = "TestClasses" }; - var d = await RunGenerator(await File.ReadAllTextAsync(inputFile), options); + var d = await RunGenerator(await File.ReadAllTextAsync(inputFile), options: options); Assert.Empty(d); Assert.False(File.Exists(Path.Combine(Path.GetTempPath(), ReportFilename))); } @@ -153,7 +152,7 @@ public async Task Should_EmitWarning_WhenPathUnavailable(bool isReportPathProvid options.Add("build_property.MetadataReportOutputPath", string.Empty); } - var diags = await RunGenerator(await File.ReadAllTextAsync(inputFile), options); + var diags = await RunGenerator(await File.ReadAllTextAsync(inputFile), options: options); var diag = Assert.Single(diags); Assert.Equal("AUDREPGEN000", diag.Id); Assert.Equal(DiagnosticSeverity.Info, diag.Severity); @@ -179,7 +178,7 @@ public async Task Should_UseProjectDir_WhenOutputPathIsRelative() ["build_property.outputpath"] = outputPath }; - var diags = await RunGenerator(await File.ReadAllTextAsync(inputFile), options); + var diags = await RunGenerator(await File.ReadAllTextAsync(inputFile), options: options); Assert.Empty(diags); Assert.True(File.Exists(Path.Combine(fullReportPath, ReportFilename))); } @@ -193,14 +192,14 @@ public async Task Should_UseProjectDir_WhenOutputPathIsRelative() /// Runs the generator on the given code. /// /// The coded that the generation will be based-on. - /// The analyzer options. + /// The output file. + /// The analyzer options. /// The cancellation Token. - /// The report file name. private static async Task> RunGenerator( string code, - Dictionary? analyzerOptions = null, - CancellationToken cancellationToken = default, - string? reportFileName = null) + string? outputFile = null, + Dictionary? options = null, + CancellationToken cancellationToken = default) { Assembly[] refs = [ @@ -213,15 +212,11 @@ private static async Task> RunGenerator( Assembly.GetAssembly(typeof(Extensions.Compliance.Classification.DataClassification))!, ]; - var generator = reportFileName is null - ? new MetadataReportsGenerator() - : new MetadataReportsGenerator(reportFileName); - var (d, _) = await RoslynTestUtils.RunGenerator( - generator, + new MetadataReportsGenerator(outputFile), refs, new[] { code, TestTaxonomy }, - new OptionsProvider(analyzerOptions), + new OptionsProvider(options), includeBaseReferences: true, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -256,4 +251,10 @@ private sealed class OptionsProvider(Dictionary? analyzerOptions public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => throw new NotSupportedException(); public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => throw new NotSupportedException(); } + + /// + /// Standardizes line endings by replacing \r\n with \n across different operating systems. + /// + private static string NormalizeEscapes(string input) => input.Replace("\r\n", "\n").Replace("\r", "\n"); + } diff --git a/test/Generators/Microsoft.Gen.MetricsReports/GoldenReports/MeterAttributedWithXmlDescriptions.json b/test/Generators/Microsoft.Gen.MetricsReports/GoldenReports/MeterAttributedWithXmlDescriptions.json index fc05721109c..65b66cddf41 100644 --- a/test/Generators/Microsoft.Gen.MetricsReports/GoldenReports/MeterAttributedWithXmlDescriptions.json +++ b/test/Generators/Microsoft.Gen.MetricsReports/GoldenReports/MeterAttributedWithXmlDescriptions.json @@ -1,31 +1,32 @@ -[ - { - "TestClasses": - [ + +[ { - "MetricName": "CounterWithDescription", - "MetricDescription": "CounterWithDescription description.", - "InstrumentName": "Counter" - }, - { - "MetricName": "HistogramWithDescription", - "MetricDescription": "HistogramWithDescription description.", - "InstrumentName": "Histogram" - }, - { - "MetricName": "HistogramWithWrongDescription", - "MetricDescription": "(Missing Summary)", - "InstrumentName": "Histogram" - }, - { - "MetricName": "ConstDescribedCounter", - "MetricDescription": "CreateConstDescribedCounter description.", - "InstrumentName": "Counter", - "Dimensions": { - "Dim4": "Dim4 description.", - "InClassDim": "InClassDim description." - } + "TestClasses": + [ + { + "MetricName": "CounterWithDescription", + "MetricDescription": "CounterWithDescription description.", + "InstrumentName": "Counter" + }, + { + "MetricName": "HistogramWithDescription", + "MetricDescription": "HistogramWithDescription description.", + "InstrumentName": "Histogram" + }, + { + "MetricName": "HistogramWithWrongDescription", + "MetricDescription": "(Missing Summary)", + "InstrumentName": "Histogram" + }, + { + "MetricName": "ConstDescribedCounter", + "MetricDescription": "CreateConstDescribedCounter description.", + "Dimensions": + { + "Dim4": "Dim4 description.", + "InClassDim": "InClassDim description." + } + } + ] } - ] - } ] \ No newline at end of file diff --git a/test/Generators/Microsoft.Gen.MetricsReports/GoldenReports/MeterDimensionsAttributedWithXmlDescriptions.json b/test/Generators/Microsoft.Gen.MetricsReports/GoldenReports/MeterDimensionsAttributedWithXmlDescriptions.json index aec09194f8a..90ae69cbae6 100644 --- a/test/Generators/Microsoft.Gen.MetricsReports/GoldenReports/MeterDimensionsAttributedWithXmlDescriptions.json +++ b/test/Generators/Microsoft.Gen.MetricsReports/GoldenReports/MeterDimensionsAttributedWithXmlDescriptions.json @@ -1,39 +1,39 @@ -[ - { - "TestClasses": - [ + +[ { - "MetricName": "DescribedDimensionCounter", - "MetricDescription": "(Missing Summary)", - "InstrumentName": "Counter", - "Dimensions": { - "Dimension1": "Dimension1 description.", - "Dim1": "" - } - }, - { - "MetricName": "DescribedDimensionHistogram", - "MetricDescription": "(Missing Summary)", - "InstrumentName": "Histogram", - "Dimensions": { - "Dimension2": "Dimension2 description.", - "DimensionDefinedInMetricClass": "DimensionDefinedInMetricClass description." - } - }, - { - "MetricName": "MyStrongTypeMetricWithDescription", - "MetricDescription": "(Missing Summary)", - "InstrumentName": "Counter", - "Dimensions": { - "AnotherDimension": "Gets or sets anotherDimension.", - "MetricEnum": "Gets or sets MetricEnum.", - "Enum2": "Gets or sets MetricEnum2.", - "Dim2": "Gets or sets Dim2.", - "dim2FromAttribute": "Gets or sets SomeDim.", - "Dim4Struct": "Gets or sets Dim4Struct.", - "Dim5FromAttribute": "Gets or sets Dim5Struct." - } + "TestClasses": + [ + { + "MetricName": "DescribedDimensionCounter", + "MetricDescription": "(Missing Summary)", + "Dimensions": + { + "Dimension1": "Dimension1 description." + } + }, + { + "MetricName": "DescribedDimensionHistogram", + "MetricDescription": "(Missing Summary)", + "Dimensions": + { + "Dimension2": "Dimension2 description.", + "DimensionDefinedInMetricClass": "DimensionDefinedInMetricClass description." + } + }, + { + "MetricName": "MyStrongTypeMetricWithDescription", + "MetricDescription": "(Missing Summary)", + "Dimensions": + { + "AnotherDimension": "Gets or sets anotherDimension.", + "MetricEnum": "Gets or sets MetricEnum.", + "Enum2": "Gets or sets MetricEnum2.", + "Dim2": "Gets or sets Dim2.", + "dim2FromAttribute": "Gets or sets SomeDim.", + "Dim4Struct": "Gets or sets Dim4Struct.", + "Dim5FromAttribute": "Gets or sets Dim5Struct." + } + } + ] } - ] - } ] \ No newline at end of file diff --git a/test/Generators/Microsoft.Gen.MetricsReports/Unit/EmitterTests.cs b/test/Generators/Microsoft.Gen.MetricsReports/Unit/EmitterTests.cs index f3207d39570..998603b0acc 100644 --- a/test/Generators/Microsoft.Gen.MetricsReports/Unit/EmitterTests.cs +++ b/test/Generators/Microsoft.Gen.MetricsReports/Unit/EmitterTests.cs @@ -128,54 +128,53 @@ public void GetMetricClassDefinition_GivenMetricTypeIsUnknown_ThrowsNotSupported [Fact] public void EmitterShouldOutputInJSONFormat() { - string newLine = Environment.NewLine; - string expected = - "[" + - newLine + " {" + - newLine + " \"MetricContainingAssembly1\":" + - newLine + " [" + - newLine + " {" + - newLine + " \"MetricName\": \"Requests\"," + - newLine + " \"MetricDescription\": \"Requests summary.\"," + - newLine + " \"InstrumentName\": \"Counter\"," + - newLine + " \"Dimensions\": {" + - newLine + " \"StatusCode\": \"Status code for request.\"," + - newLine + " \"ErrorCode\": \"Error code for request.\"" + - newLine + " }" + - newLine + " }," + - newLine + " {" + - newLine + " \"MetricName\": \"Latency\"," + - newLine + " \"MetricDescription\": \"Latency summary.\"," + - newLine + " \"InstrumentName\": \"Histogram\"," + - newLine + " \"Dimensions\": {" + - newLine + " \"Dim1\": \"\"" + - newLine + " }" + - newLine + " }," + - newLine + " {" + - newLine + " \"MetricName\": \"MemoryUsage\"," + - newLine + " \"InstrumentName\": \"Gauge\"" + - newLine + " }" + - newLine + " ]" + - newLine + " }," + - newLine + " {" + - newLine + " \"MetricContainingAssembly2\":" + - newLine + " [" + - newLine + " {" + - newLine + " \"MetricName\": \"Counter\"," + - newLine + " \"MetricDescription\": \"Counter summary.\"," + - newLine + " \"InstrumentName\": \"Counter\"" + - newLine + " }," + - newLine + " {" + - newLine + " \"MetricName\": \"Test\\\\MemoryUsage\"," + - newLine + " \"MetricDescription\": \"MemoryUsage summary.\"," + - newLine + " \"InstrumentName\": \"Gauge\"," + - newLine + " \"Dimensions\": {" + - newLine + " \"Path\": \"Test\\\\Description\\\\Path\"" + - newLine + " }" + - newLine + " }" + - newLine + " ]" + - newLine + " }" + - newLine + "]"; + string expected = @" +[ + { + ""MetricContainingAssembly1"": + [ + { + ""MetricName"": ""Requests"", + ""MetricDescription"": ""Requests summary."", + ""Dimensions"": + { + ""StatusCode"": ""Status code for request."", + ""ErrorCode"": ""Error code for request."" + } + }, + { + ""MetricName"": ""Latency"", + ""MetricDescription"": ""Latency summary."", + ""Dimensions"": + { + } + }, + { + ""MetricName"": ""MemoryUsage"", + ""InstrumentName"": ""Gauge"" + } + ] + } , +, + { + ""MetricContainingAssembly2"": + [ + { + ""MetricName"": ""Counter"", + ""MetricDescription"": ""Counter summary."", + ""InstrumentName"": ""Counter"" + }, + { + ""MetricName"": ""Test\\\\MemoryUsage"", + ""MetricDescription"": ""MemoryUsage summary."", + ""Dimensions"": + { + ""Path"": ""Test\\\\Description\\\\Path"" + } + } + ] + } +]"; var emitter = new MetricDefinitionEmitter(); string json = emitter.GenerateReport(_metricClasses, CancellationToken.None); diff --git a/test/Generators/Microsoft.Gen.MetricsReports/Unit/GeneratorTests.cs b/test/Generators/Microsoft.Gen.MetricsReports/Unit/GeneratorTests.cs index f432257b111..b8fe17b6046 100644 --- a/test/Generators/Microsoft.Gen.MetricsReports/Unit/GeneratorTests.cs +++ b/test/Generators/Microsoft.Gen.MetricsReports/Unit/GeneratorTests.cs @@ -41,23 +41,22 @@ public async Task TestAll(bool useExplicitReportPath) 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); + string stem = Path.GetFileNameWithoutExtension(inputFile); + string goldenFileName = Path.ChangeExtension(stem, ".json"); + string goldenReportPath = Path.Combine("GoldenReports", goldenFileName); if (File.Exists(goldenReportPath)) { - var d = await RunGenerator(await File.ReadAllTextAsync(inputFile), options); - Assert.Empty(d); + string temporaryReportFile = Path.GetTempFileName(); + var diagnostic = await RunGenerator(await File.ReadAllTextAsync(inputFile), temporaryReportFile, options); + Assert.Empty(diagnostic); - var golden = await File.ReadAllTextAsync(goldenReportPath); - var generated = await File.ReadAllTextAsync(generatedReportPath); + string golden = await File.ReadAllTextAsync(goldenReportPath); + string generated = await File.ReadAllTextAsync(temporaryReportFile); if (golden != generated) { - output.WriteLine($"MISMATCH: golden report {goldenReportPath}, generated {generatedReportPath}"); + output.WriteLine($"MISMATCH: golden report {goldenReportPath}, generated {temporaryReportFile}"); output.WriteLine("----"); output.WriteLine("golden:"); output.WriteLine(golden); @@ -67,14 +66,14 @@ public async Task TestAll(bool useExplicitReportPath) output.WriteLine("----"); } - File.Delete(generatedReportPath); + File.Delete(temporaryReportFile); 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); + _ = await RunGenerator(await File.ReadAllTextAsync(inputFile), goldenFileName, options); } } } @@ -89,7 +88,7 @@ public async Task ShouldNot_Generate_WhenDisabledViaConfig() ["build_property.MetricsReportOutputPath"] = Path.GetTempPath() }; - var d = await RunGenerator(await File.ReadAllTextAsync(inputFile), options); + var d = await RunGenerator(await File.ReadAllTextAsync(inputFile), options: options); Assert.Empty(d); Assert.False(File.Exists(Path.Combine(Path.GetTempPath(), ReportFilename))); } @@ -109,7 +108,7 @@ public async Task Should_EmitWarning_WhenPathUnavailable(bool isReportPathProvid options.Add("build_property.MetricsReportOutputPath", string.Empty); } - var diags = await RunGenerator(await File.ReadAllTextAsync(inputFile), options); + var diags = await RunGenerator(await File.ReadAllTextAsync(inputFile), options: options); var diag = Assert.Single(diags); Assert.Equal("AUDREPGEN000", diag.Id); Assert.Equal(DiagnosticSeverity.Info, diag.Severity); @@ -132,7 +131,7 @@ public async Task Should_UseProjectDir_WhenOutputPathIsRelative() ["build_property.outputpath"] = outputPath }; - var diags = await RunGenerator(await File.ReadAllTextAsync(inputFile), options); + var diags = await RunGenerator(await File.ReadAllTextAsync(inputFile), options: options); Assert.Empty(diags); Assert.True(File.Exists(Path.Combine(fullReportPath, ReportFilename))); } @@ -142,11 +141,18 @@ public async Task Should_UseProjectDir_WhenOutputPathIsRelative() } } + /// + /// Runs the generator on the given code. + /// + /// The coded that the generation will be based-on. + /// The output file. + /// The analyzer options. + /// The cancellation Token. private static async Task> RunGenerator( string code, - Dictionary? analyzerOptions = null, - CancellationToken cancellationToken = default, - string? reportFileName = null) + string? outputFile = null, + Dictionary? options = null, + CancellationToken cancellationToken = default) { Assembly[] refs = [ @@ -156,15 +162,11 @@ private static async Task> RunGenerator( Assembly.GetAssembly(typeof(GaugeAttribute))! ]; - var generator = reportFileName is null - ? new MetricsReportsGenerator() - : new MetricsReportsGenerator(reportFileName); - var (d, _) = await RoslynTestUtils.RunGenerator( - generator, + new MetricsReportsGenerator(outputFile), refs, new[] { code }, - new OptionsProvider(analyzerOptions), + new OptionsProvider(options), includeBaseReferences: true, cancellationToken: cancellationToken).ConfigureAwait(false);