| File: GeneratorTests.cs | Web Access |
| Project: src\test\Generators\Microsoft.Gen.ComplianceReports\Unit\Microsoft.Gen.ComplianceReports.Unit.Tests.csproj (Microsoft.Gen.ComplianceReports.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.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.Extensions.Logging; using Microsoft.Gen.Shared; using Xunit; using Xunit.Abstractions; namespace Microsoft.Gen.ComplianceReports.Tests; public class GeneratorTests { private const string TestTaxonomy = @" using Microsoft.Extensions.Compliance.Classification; public sealed class C1Attribute : DataClassificationAttribute { public C1Attribute(new DataClassification(""TAX"", 1)) { } } public sealed class C2Attribute : DataClassificationAttribute { public C2Attribute(new DataClassification(""TAX"", 2)) { } } public sealed class C3Attribute : DataClassificationAttribute { public C3Attribute(new DataClassification(""TAX"", 4)) { } } public sealed class C4Attribute : DataClassificationAttribute { public C4Attribute(new DataClassification(""TAX"", 8)) { } } "; private readonly ITestOutputHelper _output; public GeneratorTests(ITestOutputHelper output) { _output = output; } [Theory] [CombinatorialData] public async Task TestAll(bool useExplicitReportPath) { Dictionary<string, string>? options = useExplicitReportPath ? new() { ["build_property.ComplianceReportOutputPath"] = Directory.GetCurrentDirectory() } : null; foreach (var inputFile in Directory.GetFiles("TestClasses")) { var stem = Path.GetFileNameWithoutExtension(inputFile); var goldenReportFile = $"GoldenReports/{stem}.json"; if (File.Exists(goldenReportFile)) { var tmp = Path.GetTempFileName(); var d = await RunGenerator(File.ReadAllText(inputFile), tmp, options); Assert.Empty(d); var golden = File.ReadAllText(goldenReportFile); var generated = File.ReadAllText(tmp); if (golden != generated) { _output.WriteLine($"MISMATCH: golden report {goldenReportFile}, generated {tmp}"); _output.WriteLine("----"); _output.WriteLine("golden:"); _output.WriteLine(golden); _output.WriteLine("----"); _output.WriteLine("generated:"); _output.WriteLine(generated); _output.WriteLine("----"); } File.Delete(tmp); Assert.Equal(golden, generated); } else { // generate the golden file if it doesn't already exist _output.WriteLine($"Generating golden report: {goldenReportFile}"); _ = await RunGenerator(File.ReadAllText(inputFile), goldenReportFile, options); } } } [Fact] public async Task MissingDataClassificationSymbol() { const string Source = "class Nothing {}"; var (d, _) = await RoslynTestUtils.RunGenerator( new ComplianceReportsGenerator("Foo"), null, new[] { Source, }, new OptionsProvider(null)) ; Assert.Empty(d); } [Theory] [CombinatorialData] public async Task Should_EmitWarning_WhenPathUnavailable(bool isReportPathProvided) { var inputFile = Directory.GetFiles("TestClasses").First(); var options = new Dictionary<string, string> { ["build_property.outputpath"] = string.Empty }; if (isReportPathProvided) { options.Add("build_property.ComplianceReportOutputPath", string.Empty); } var diags = await RunGenerator(await File.ReadAllTextAsync(inputFile), options: options); var diag = Assert.Single(diags); Assert.Equal("AUDREPGEN001", diag.Id); Assert.Equal(DiagnosticSeverity.Info, diag.Severity); } [Fact] public async Task Should_UseProjectDir_WhenOutputPathIsRelative() { var projectDir = Path.GetTempPath(); var outputPath = Guid.NewGuid().ToString(); var fullReportPath = Path.Combine(projectDir, outputPath); Directory.CreateDirectory(fullReportPath); try { var inputFile = Directory.GetFiles("TestClasses").First(); var options = new Dictionary<string, string> { ["build_property.projectdir"] = projectDir, ["build_property.outputpath"] = outputPath }; var diags = await RunGenerator(await File.ReadAllTextAsync(inputFile), options: options); Assert.Empty(diags); Assert.True(File.Exists(Path.Combine(fullReportPath, "ComplianceReport.json"))); } finally { Directory.Delete(fullReportPath, recursive: true); } } private static async Task<IReadOnlyList<Diagnostic>> RunGenerator(string code, string? outputFile = null, Dictionary<string, string>? options = null) { var (d, _) = await RoslynTestUtils.RunGenerator( new ComplianceReportsGenerator(outputFile), new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))!, Assembly.GetAssembly(typeof(Extensions.Compliance.Classification.DataClassification))!, }, new[] { code, TestTaxonomy, }, new OptionsProvider(analyzerOptions: options)).ConfigureAwait(false); return d; } private sealed class Options : AnalyzerConfigOptions { private readonly Dictionary<string, string> _options; public Options(Dictionary<string, string>? analyzerOptions) { _options = analyzerOptions ?? []; _options.TryAdd("build_property.GenerateComplianceReport", bool.TrueString); _options.TryAdd("build_property.outputpath", Directory.GetCurrentDirectory()); } public override bool TryGetValue(string key, out string value) => _options.TryGetValue(key, out value!); } private sealed class OptionsProvider(Dictionary<string, string>? analyzerOptions) : AnalyzerConfigOptionsProvider { public override AnalyzerConfigOptions GlobalOptions => new Options(analyzerOptions); public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => throw new System.NotSupportedException(); public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => throw new System.NotSupportedException(); } }