Tag Archives: unit testing

Roslyn analyzer with external references

I was creating a Roslyn powered analyzer to detect proper usage of WinForms components that are not added to a form through designer. When you add control through designer it is properly disposed when the form itself is disposed. The designer also adds field components of type System.ComponentModel.IContainer that is disposed in Dispose method:

protected override void Dispose(bool disposing)
{
  if (disposing && (components != null))
  {
    components.Dispose();
  }
  base.Dispose(disposing);
} 

But if you create, for example a timer, manually, you will have to dispose it manually later. Instead of handling the disposal of that timer this way, it is easier to add it to that container where it will be disposed along with all other controls.

The analyzer worked fine when a new instance of VS.NET was triggered and the debugger was actually debugging the code running in that instance. But this process is somewhat slow and you spend some time waiting for the new instance of VS.NET to be open.

When you create new Roslyn analyzer project using the project template, a test project is created as well with ready-to-run code. Because it is much easier to do it TDD way, I created new test based on the test classes already available. But my tests were failing.

I quickly discovered the problem – in the analyzer code, there is some type checking when I do not want to carry on with the analysis when the class being analyzed is not inherited from System.Windows.Forms class:

var formType = context.SemanticModel.Compilation.GetTypeByMetadataName("System.Windows.Forms.Form");

and the reason the test was failing was that formType is null and that’s because there is no metadata available when the test is executed. But it does not fail in the second instance of VS.NET, because the project is fully loaded with all references there.

So I went straight to Roslyn GitHub repository and searched for mscorlib, System.Linq, etc. and found out that the tests there are adding references through properties like this one:

private static MetadataReference s_systemWindowsFormsRef;
public static MetadataReference SystemWindowsFormsRef
{
  get
  {
    if (s_systemWindowsFormsRef == null)
    {
      s_systemWindowsFormsRef = AssemblyMetadata.CreateFromImage(TestResources.NetFX.v4_0_30319.System_Windows_Forms).GetReference(display: "System.Windows.Forms.v4_0_30319.dll");
    }

    return s_systemWindowsFormsRef;
  }
}

And the most important code here is AssemblyMetadata.CreateFromImage(TestResources.NetFX.v4_0_30319.System_Windows_Forms) which creates the assembly metadata object for System.Windows.Forms assembly. After cloning the solution I discovered that the TestResources namespace comes from external assembly Microsoft.CodeAnalysis.Test.Resources.Proprietary that was added as a Nuget package.

This library contains assembly metadata images for selected .NET framework versions and assemblies (System, System.Data, System.Drawing, …). Note that the only version of that library available is prerelease version.

What was left was to change the code generated by project template to propagate the references

VerifyCSharpDiagnostic(test, new[]
{
  AssemblyMetadata.CreateFromImage(TestResources.NetFX.v4_0_30319.System_Windows_Forms)
    .GetReference(display: "System.Windows.Forms.v4_0_30319.dll"),
  AssemblyMetadata.CreateFromImage(TestResources.NetFX.v4_0_30319.System)
    .GetReference(display: "System.v4_0_30319.dll")
}, expected); 

deeper to where the Compilation is created and change the code there:

var compilation = CSharpCompilation.Create(
   "MyName",
   documents.Select(d => d.GetSyntaxTreeAsync().Result),
   references, // there are those references
   new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Debug));
// and pass the analyzer 
var compilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(analyzer));

And then my unit tests worked! I recommend you to check the unit testing part of Roslyn project for some guidelines on how to test the analyzers and also how to write reusable test components (and rewrite is something my code needs right now).