| File: GeneratorTests.cs | Web Access |
| Project: src\test\Generators\Microsoft.Gen.BuildMetadata\Unit\Microsoft.Gen.BuildMetadata.Unit.Tests.csproj (Microsoft.Gen.BuildMetadata.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.CodeAnalysis; using System.Linq; using System.Reflection; using System.Threading.Tasks; using FluentAssertions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.Gen.Shared; using VerifyTests; using VerifyXunit; using Xunit; namespace Microsoft.Gen.BuildMetadata.Test; [Collection("BuildMetadataEmitterTests")] public class GeneratorTests { private readonly VerifySettings _verifySettings; public GeneratorTests() { _verifySettings = new VerifySettings(); _verifySettings.UseDirectory("Verified"); _verifySettings.ScrubLinesWithReplace(value => { if (value.Contains(GeneratorUtilities.GeneratedCodeAttribute)) { return value.Replace(GeneratorUtilities.CurrentVersion, "VERSION"); } return value; }); } [Theory] [CombinatorialData] public async Task BuildMetadataGenerator_ShouldGenerate([CombinatorialValues(true, false, null)] bool? isAzureDevOps) { var source = string.Empty; // Empty source, no attributes // Create test options based on the isAzureDevOps parameter var optionsProvider = CreateTestOptionsProvider(isAzureDevOps); var (d, sources) = await RunGenerator(source, optionsProvider); d.Should().BeEmpty(); sources.Should().HaveCount(1); var settings = new VerifySettings(_verifySettings); settings.DisableRequireUniquePrefix(); settings.UseParameters(isAzureDevOps); await Verifier.Verify(sources.Select(s => s.SourceText.ToString()), settings); } private static TestAnalyzerConfigOptionsProvider CreateTestOptionsProvider(bool? isAzureDevOps) { return new TestAnalyzerConfigOptionsProvider(new Dictionary<string, string?> { { "build_property.BuildMetadataAzureBuildId", "TEST_AZURE_BUILDID" }, { "build_property.BuildMetadataAzureBuildNumber", "TEST_AZURE_BUILDNUMBER" }, { "build_property.BuildMetadataAzureSourceBranchName", "TEST_AZURE_SOURCEBRANCHNAME" }, { "build_property.BuildMetadataAzureSourceVersion", "TEST_AZURE_SOURCEVERSION" }, { "build_property.BuildMetadataIsAzureDevOps", isAzureDevOps?.ToString() ?? "false" }, { "build_property.BuildMetadataGitHubRunId", "TEST_GITHUB_RUNID" }, { "build_property.BuildMetadataGitHubRunNumber", "TEST_GITHUB_RUNNUMBER" }, { "build_property.BuildMetadataGitHubRefName", "TEST_GITHUB_REFNAME" }, { "build_property.BuildMetadataGitHubSha", "TEST_GITHUB_SHA" } }); } private static async Task<(IReadOnlyList<Diagnostic> diagnostics, IReadOnlyList<GeneratedSourceResult> sources)> RunGenerator( string source, TestAnalyzerConfigOptionsProvider optionsProvider) { // Create a test project and compilation var proj = RoslynTestUtils.CreateTestProject(Array.Empty<Assembly>()); proj = proj.WithDocument("source.cs", source); proj.CommitChanges(); var comp = await proj.GetCompilationAsync(); // Create the generator driver with the options provider var driver = Microsoft.CodeAnalysis.CSharp.CSharpGeneratorDriver.Create( generators: new[] { new BuildMetadataGenerator().AsSourceGenerator() }, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview), optionsProvider: optionsProvider); var result = driver.RunGeneratorsAndUpdateCompilation(comp!, out var outputCompilation, out var diagnostics); var runResult = result.GetRunResult(); // Verify the generated code compiles, allowing only expected missing reference errors var compilationDiagnostics = outputCompilation.GetDiagnostics() .Where(d => d.Severity >= DiagnosticSeverity.Warning) .ToList(); // The generated code references Microsoft.Extensions.Hosting and related packages // which aren't available in the test compilation context. We allow CS0234 (missing namespace) // and CS1061 (missing member) errors as they're expected, but fail on any other errors. var unexpectedDiagnostics = compilationDiagnostics .Where(d => !IsExpectedCompilationError(d)) .ToList(); unexpectedDiagnostics.Should().BeEmpty( because: "generated code should not have syntax or semantic errors beyond expected missing references, but found: {0}", string.Join(", ", unexpectedDiagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"))); return (diagnostics, runResult.Results[0].GeneratedSources); } private static bool IsExpectedCompilationError(Diagnostic diagnostic) { // CS0234: The type or namespace name does not exist (missing assembly reference) // CS1061: Type does not contain a definition for member (missing assembly reference) // These are expected because the generated code uses Microsoft.Extensions types // that aren't available in the test compilation context return diagnostic.Id is "CS0234" or "CS1061"; } private sealed class TestAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider { private readonly TestAnalyzerConfigOptions _globalOptions; public TestAnalyzerConfigOptionsProvider(Dictionary<string, string?> globalOptions) { _globalOptions = new TestAnalyzerConfigOptions(globalOptions); } public override AnalyzerConfigOptions GlobalOptions => _globalOptions; public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => _globalOptions; public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => _globalOptions; } private sealed class TestAnalyzerConfigOptions : AnalyzerConfigOptions { private readonly Dictionary<string, string?> _options; public TestAnalyzerConfigOptions(Dictionary<string, string?> options) { _options = options; } public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { return _options.TryGetValue(key, out value); } public override IEnumerable<string> Keys => _options.Keys; } }