Skip to content
Prev Previous commit
Next Next commit
Add unit test to Logging generator
  • Loading branch information
steveharter committed Sep 27, 2021
commit ff6785057926fd0431becc8a82224a7f4e716d40
25 changes: 25 additions & 0 deletions src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,31 @@ public static TextSpan MakeSpan(string text, int spanNum)
return (r.Results[0].Diagnostics, r.Results[0].GeneratedSources);
}

/// <summary>
/// Runs a Roslyn generator given a Compilation.
/// </summary>
public static (ImmutableArray<Diagnostic>, ImmutableArray<GeneratedSourceResult>) RunGenerator(
Compilation compilation,
#if ROSLYN4_0_OR_GREATER
IIncrementalGenerator generator)
#else
ISourceGenerator generator)
#endif
{
#if ROSLYN4_0_OR_GREATER
// workaround https://github.com/dotnet/roslyn/pull/55866. We can remove "LangVersion=Preview" when we get a Roslyn build with that change.
CSharpParseOptions options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview);
CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator.AsSourceGenerator() }, parseOptions: options);
#else
CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator });
#endif

GeneratorDriver gd = cgd.RunGenerators(compilation);

GeneratorDriverRunResult r = gd.GetRunResult();
return (r.Results[0].Diagnostics, r.Results[0].GeneratedSources);
}

/// <summary>
/// Runs a Roslyn analyzer over a set of source files.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Microsoft.Extensions.Logging.Generators.Tests
{
public class CompilationHelper
{
public static Compilation CreateCompilation(
string source,
MetadataReference[]? additionalReferences = null,
string assemblyName = "TestAssembly")
{
string corelib = Assembly.GetAssembly(typeof(object))!.Location;
string runtimeDir = Path.GetDirectoryName(corelib)!;

var refs = new List<MetadataReference>();
refs.Add(MetadataReference.CreateFromFile(corelib));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "netstandard.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "System.Runtime.dll")));
refs.Add(MetadataReference.CreateFromFile(typeof(ILogger).Assembly.Location));
refs.Add(MetadataReference.CreateFromFile(typeof(LoggerMessageAttribute).Assembly.Location));

// Add additional references as needed.
if (additionalReferences != null)
{
foreach (MetadataReference reference in additionalReferences)
{
refs.Add(reference);
}
}

CSharpParseOptions options = new CSharpParseOptions(
kind: SourceCodeKind.Regular,
documentationMode: DocumentationMode.Parse);

#if ROSLYN4_0_OR_GREATER
// workaround https://github.com/dotnet/roslyn/pull/55866. We can remove "LangVersion=Preview" when we get a Roslyn build with that change.
options = options.WithLanguageVersion(LanguageVersion.Preview);
#endif

return CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, options) },
references: refs.ToArray(),
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
);
}

public static byte[] CreateAssemblyImage(Compilation compilation)
{
MemoryStream ms = new MemoryStream();
var emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
throw new InvalidOperationException();
}
return ms.ToArray();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -679,6 +681,59 @@ static partial class C
Assert.Empty(diagnostics); // should fail quietly on broken code
}

[Fact]
public void TestMultipleDefinitions()
{
// Adding a dependency to an assembly that has internal definitions of public types
// should not result in a collision and break generation.
// Verify usage of the extension GetBestTypeByMetadataName(this Compilation) instead of Compilation.GetTypeByMetadataName().
var referencedSource = @"
namespace System
{
internal class Exception { }
}
namespace Microsoft.Extensions.Logging
{
internal class LoggerMessageAttribute { }
}
namespace Microsoft.Extensions.Logging
{
internal interface ILogger { }
internal enum LogLevel { }
}";

// Compile the referenced assembly first.
Compilation referencedCompilation = CompilationHelper.CreateCompilation(referencedSource);

// Obtain the image of the referenced assembly.
byte[] referencedImage = CompilationHelper.CreateAssemblyImage(referencedCompilation);

// Generate the code
string source = @"
namespace Test
{
using Microsoft.Extensions.Logging;

partial class C
{
[LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = ""M1"")]
static partial void M1(ILogger logger);
}
}";

MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(referencedImage) };
Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences);
LoggerMessageGenerator generator = new LoggerMessageGenerator();

(ImmutableArray<Diagnostic> diagnostics, ImmutableArray<GeneratedSourceResult> generatedSources) =
RoslynTestUtils.RunGenerator(compilation, generator);

// Make sure compilation was successful.
Assert.Empty(diagnostics.Where(diag => diag.Severity.Equals(DiagnosticSeverity.Error)));
Assert.Equal(1, generatedSources.Length);
Assert.Equal(21, generatedSources[0].SourceText.Lines.Count);
}

private static async Task<IReadOnlyList<Diagnostic>> RunGenerator(
string code,
bool wrap = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,16 +487,7 @@ internal class JsonSourceGenerationOptionsAttribute { }
Compilation referencedCompilation = CompilationHelper.CreateCompilation(referencedSource);

// Obtain the image of the referenced assembly.
byte[] referencedImage;
using (MemoryStream ms = new MemoryStream())
{
var emitResult = referencedCompilation.Emit(ms);
if (!emitResult.Success)
{
throw new InvalidOperationException();
}
referencedImage = ms.ToArray();
}
byte[] referencedImage = CompilationHelper.CreateAssemblyImage(referencedCompilation);

// Generate the code
string source = @"
Expand Down