Skip to content

Commit

Permalink
Convert SourceGenerator_RazorFiles_Works to Microsoft.CodeAnalysis.Te…
Browse files Browse the repository at this point in the history
…sting
  • Loading branch information
sharwell committed Mar 3, 2023
1 parent cd9273f commit f30923f
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 36 deletions.
3 changes: 2 additions & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
<!-- Several packages from the editor are used for testing HTML support, and share the following version. -->
<Tooling_HtmlEditorPackageVersion>17.5.101-preview-0002</Tooling_HtmlEditorPackageVersion>
<!-- Several packages share the MS.CA.Testing version -->
<Tooling_MicrosoftCodeAnalysisTestingVersion>1.1.2-beta1.22512.1</Tooling_MicrosoftCodeAnalysisTestingVersion>
<Tooling_MicrosoftCodeAnalysisTestingVersion>1.1.2-beta1.23115.1</Tooling_MicrosoftCodeAnalysisTestingVersion>
<MicrosoftVisualStudioShellPackagesVersion>17.5.0-preview-2-33117-317</MicrosoftVisualStudioShellPackagesVersion>
<MicrosoftVisualStudioPackagesVersion>17.5.274-preview</MicrosoftVisualStudioPackagesVersion>
<RoslynPackageVersion>4.6.0-2.23113.15</RoslynPackageVersion>
Expand All @@ -96,6 +96,7 @@
<MicrosoftNETSdkRazorPackageVersion>6.0.0-alpha.1.21072.5</MicrosoftNETSdkRazorPackageVersion>
<!-- Packages from dotnet/roslyn -->
<MicrosoftCodeAnalysisAnalyzerTestingPackageVersion>$(Tooling_MicrosoftCodeAnalysisTestingVersion)</MicrosoftCodeAnalysisAnalyzerTestingPackageVersion>
<MicrosoftCodeAnalysisCSharpSourceGeneratorsTestingXUnitPackageVersion>$(Tooling_MicrosoftCodeAnalysisTestingVersion)</MicrosoftCodeAnalysisCSharpSourceGeneratorsTestingXUnitPackageVersion>
<MicrosoftCodeAnalysisTestingVerifiersXunitPackageVersion>$(Tooling_MicrosoftCodeAnalysisTestingVersion)</MicrosoftCodeAnalysisTestingVerifiersXunitPackageVersion>
<MicrosoftVisualStudioEditorPackageVersion>$(MicrosoftVisualStudioPackagesVersion)</MicrosoftVisualStudioEditorPackageVersion>
<MicrosoftVisualStudioExtensibilityTestingXunitVersion>0.1.149-beta</MicrosoftVisualStudioExtensibilityTestingXunitVersion>
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCoreVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="$(MicrosoftCodeAnalysisCommonPackageVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisCSharpPackageVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="$(MicrosoftCodeAnalysisCSharpSourceGeneratorsTestingXUnitPackageVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="$(MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion)" />
<PackageVersion Include="Microsoft.CSharp" Version="$(MicrosoftCSharpVersion)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<Compile Include="**\*.cs" Exclude="$(GlobalExclude)" />
</ItemGroup>

<ItemGroup>
<Compile Remove="Resources\**\*.cs" />
<EmbeddedResource Include="Resources\**\*.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.Build.Framework" />
Expand All @@ -26,6 +31,7 @@
<PackageReference Include="xunit.assert" />
<PackageReference Include="xunit.extensibility.execution" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,23 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.DependencyModel.Resolution;
using Xunit;
using Xunit.Sdk;

namespace Microsoft.NET.Sdk.Razor.SourceGenerators
{
using Verify = Verifiers.CSharpSourceGeneratorVerifier<RazorSourceGenerator>;

public class RazorSourceGeneratorTests
{
private static readonly Project _baseProject = CreateBaseProject();
Expand All @@ -31,42 +34,18 @@ public class RazorSourceGeneratorTests
public async Task SourceGenerator_RazorFiles_Works()
{
// Arrange
var project = CreateTestProject(new()
var test = new RazorTest
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
});

var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project);

var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line hidden
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
");
TestState =
{
AdditionalFiles =
{
("/0/Pages/Index.razor", "<h1>Hello world</h1>")
},
},
};

Assert.Empty(result.Diagnostics);
Assert.Single(result.GeneratedSources);
await test.AddMetadata().AddGeneratedSources().RunAsync();
}

internal class InMemoryAdditionalText : AdditionalText
Expand Down Expand Up @@ -2512,6 +2491,43 @@ public bool TryResolveAssemblyPaths(CompilationLibrary library, List<string>? as
}
}

private class RazorTest : Verify.Test
{
public RazorTest([CallerFilePath] string? testFile = null, [CallerMemberName] string? testMethod = null)
: base(testFile, testMethod)
{
// Don't resolve any reference assemblies from NuGet
ReferenceAssemblies = new ReferenceAssemblies("custom");

foreach (var defaultCompileLibrary in DependencyContext.Load(typeof(RazorSourceGeneratorTests).Assembly)!.CompileLibraries)
{
foreach (var resolveReferencePath in defaultCompileLibrary.ResolveReferencePaths(new AppLocalResolver()))
{
TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(resolveReferencePath));
}
}

// The deps file in the project is incorrect and does not contain "compile" nodes for some references.
// However these binaries are always present in the bin output. As a "temporary" workaround, we'll add
// every dll file that's present in the test's build output as a metadatareference.
foreach (var assembly in Directory.EnumerateFiles(AppContext.BaseDirectory, "*.dll"))
{
if (!TestState.AdditionalReferences.Any(c => string.Equals(Path.GetFileNameWithoutExtension(c.Display), Path.GetFileNameWithoutExtension(assembly), StringComparison.OrdinalIgnoreCase)))
{
TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(assembly));
}
}
}

public NullableContextOptions NullableContextOptions { get; set; } = NullableContextOptions.Enable;

protected override CodeAnalysis.CompilationOptions CreateCompilationOptions()
{
var options = (CSharpCompilationOptions)base.CreateCompilationOptions();
return options.WithNullableContextOptions(NullableContextOptions);
}
}

private static Project CreateBaseProject()
{
var projectId = ProjectId.CreateNewId(debugName: "TestProject");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma checksum "/0/Pages/Index.razor" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "6b5db227a6aa2228c777b0771108b184b1fc5df3"
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line hidden
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, "<h1>Hello world</h1>");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

// Uncomment the following line to write expected files to disk
////#define WRITE_EXPECTED

#if WRITE_EXPECTED
#warning WRITE_EXPECTED is fine for local builds, but should not be merged to the main branch.
#endif

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;

namespace Microsoft.NET.Sdk.Razor.SourceGenerators.Verifiers
{
public static partial class CSharpSourceGeneratorVerifier<TSourceGenerator>
where TSourceGenerator : IIncrementalGenerator, new()
{
public class Test : CSharpSourceGeneratorTest<EmptySourceGeneratorProvider, XUnitVerifier>
{
private readonly string? _testFile;
private readonly string? _testMethod;

public Test([CallerFilePath] string? testFile = null, [CallerMemberName] string? testMethod = null)
{
CompilerDiagnostics = CompilerDiagnostics.Warnings;

_testFile = testFile;
_testMethod = testMethod;

#if WRITE_EXPECTED
TestBehaviors |= TestBehaviors.SkipGeneratedSourcesCheck;
#endif
}

public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.Default;

protected override IEnumerable<Type> GetSourceGenerators()
{
yield return typeof(TSourceGenerator);
}

protected override CompilationOptions CreateCompilationOptions()
{
var compilationOptions = (CSharpCompilationOptions)base.CreateCompilationOptions();
return compilationOptions
.WithAllowUnsafe(false)
.WithWarningLevel(99)
.WithSpecificDiagnosticOptions(compilationOptions.SpecificDiagnosticOptions.SetItem("CS8019", ReportDiagnostic.Suppress));
}

protected override ParseOptions CreateParseOptions()
{
return ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(LanguageVersion);
}

protected override async Task<(Compilation compilation, ImmutableArray<Diagnostic> generatorDiagnostics)> GetProjectCompilationAsync(Project project, IVerifier verifier, CancellationToken cancellationToken)
{
var resourceDirectory = Path.Combine(Path.GetDirectoryName(_testFile)!, "Resources", _testMethod!);

var (compilation, generatorDiagnostics) = await base.GetProjectCompilationAsync(project, verifier, cancellationToken);
var expectedNames = new HashSet<string>();
foreach (var tree in compilation.SyntaxTrees.Skip(project.DocumentIds.Count))
{
WriteTreeToDiskIfNecessary(tree, resourceDirectory);
expectedNames.Add(Path.GetFileName(tree.FilePath));
}

var currentTestPrefix = $"{typeof(RazorSourceGeneratorTests).Assembly.GetName().Name}.Resources.{_testMethod}.";
foreach (var name in GetType().Assembly.GetManifestResourceNames())
{
if (!name.StartsWith(currentTestPrefix, StringComparison.Ordinal))
{
continue;
}

if (!expectedNames.Contains(name[currentTestPrefix.Length..]))
{
throw new InvalidOperationException($"Unexpected test resource: {name[currentTestPrefix.Length..]}");
}
}

return (compilation, generatorDiagnostics);
}

public Test AddMetadata()
{
var globalConfig = new StringBuilder(@"is_global = true
build_property.RazorConfiguration = Default
build_property.RootNamespace = MyApp
build_property.RazorLangVersion = Latest
build_property.GenerateRazorMetadataSourceChecksumAttributes = false
");

foreach (var (filename, _) in TestState.AdditionalFiles)
{
globalConfig.AppendLine(CultureInfo.InvariantCulture, $@"[{filename}]
build_metadata.AdditionalFiles.TargetPath = {Convert.ToBase64String(Encoding.UTF8.GetBytes(getRelativeFilePath(filename)))}");
}

TestState.AnalyzerConfigFiles.Add(("/.globalconfig", globalConfig.ToString()));

return this;

static string getRelativeFilePath(string absolutePath)
{
if (absolutePath.StartsWith("/0/", StringComparison.Ordinal))
{
return absolutePath["/0/".Length..];
}
else if (absolutePath.StartsWith("/", StringComparison.Ordinal))
{
return absolutePath["/".Length..];
}
else
{
return absolutePath;
}
}
}

/// <summary>
/// Loads expected generated sources from embedded resources based on the test name.
/// </summary>
/// <param name="testMethod">The current test method name.</param>
/// <returns>The current <see cref="Test"/> instance.</returns>
public Test AddGeneratedSources([CallerMemberName] string? testMethod = null)
{
var expectedPrefix = $"{typeof(RazorSourceGeneratorTests).Assembly.GetName().Name}.Resources.{testMethod}.";
foreach (var resourceName in typeof(Test).Assembly.GetManifestResourceNames())
{
if (!resourceName.StartsWith(expectedPrefix, StringComparison.Ordinal))
{
continue;
}

using var resourceStream = typeof(RazorSourceGeneratorTests).Assembly.GetManifestResourceStream(resourceName) ?? throw new InvalidOperationException();
using var reader = new StreamReader(resourceStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 4096, leaveOpen: true);
var name = resourceName[expectedPrefix.Length..];
TestState.GeneratedSources.Add((typeof(RazorSourceGenerator), name, reader.ReadToEnd()));
}

// An error will be reported if there are no sources or generated sources in the compilation. To bypass
// during the initial test construction, we add a default empty generated source knowing that it will
// not be validated.
if (TestBehaviors.HasFlag(TestBehaviors.SkipGeneratedSourcesCheck) && !TestState.Sources.Any() && !TestState.GeneratedSources.Any())
{
TestState.GeneratedSources.Add(("/ignored_file", ""));
}

return this;
}

[Conditional("WRITE_EXPECTED")]
private static void WriteTreeToDiskIfNecessary(SyntaxTree tree, string resourceDirectory)
{
if (tree.Encoding is null)
{
throw new ArgumentException("Syntax tree encoding was not specified");
}

var name = Path.GetFileName(tree.FilePath);
var filePath = Path.Combine(resourceDirectory, name);
Directory.CreateDirectory(resourceDirectory);
File.WriteAllText(filePath, tree.GetText().ToString(), tree.Encoding);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.CodeAnalysis;

namespace Microsoft.NET.Sdk.Razor.SourceGenerators.Verifiers
{
public static partial class CSharpSourceGeneratorVerifier<TSourceGenerator>
where TSourceGenerator : IIncrementalGenerator, new()
{
}
}

0 comments on commit f30923f

Please sign in to comment.