From f425134ffebf57b2f586bb78155c6efb56b9335c Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 26 Sep 2018 22:51:51 -0700 Subject: [PATCH] Fix #2265 - Port Blazor testing improvements The Blazor test infrastructure made a copy of the code in Razor and then added some more features. This change backports the features needed for the style of test we're using in Blazor. I updated the MVC integration tests to use the new style, but I think there's limited value in trying to rev all of the old tests. One feature in particular that I removed from the old infrastructure was the automatic inference of imports based on the file system. This feature was wierd and doesn't parallel how these features work in actuality. It's easy and more natural to test imports in new style tests. --- .../CodeGenerationIntegrationTest.cs | 786 +++++++++++------- .../InstrumentationPassIntegrationTest.cs | 2 +- .../InheritsWithViewImports_DesignTime.ir.txt | 2 +- .../InheritsWithViewImports_Imports0.cshtml | 1 - ...InheritsWithViewImports_Runtime.codegen.cs | 2 +- .../CodeGenerationIntegrationTest.cs | 369 ++++---- .../InheritsWithViewImports_DesignTime.ir.txt | 2 +- .../InheritsWithViewImports_Imports0.cshtml | 1 - .../IntegrationTests/BasicIntegrationTest.cs | 66 +- .../CodeGenerationIntegrationTest.cs | 21 +- .../ExtensibleDirectiveTest.cs | 2 +- .../HtmlAttributeIntegrationTest.cs | 14 +- .../TagHelpersIntegrationTest.cs | 6 +- .../BasicImports.cshtml | 1 - .../BasicImports_DesignTime.codegen.cs | 37 - .../BasicImports_DesignTime.ir.txt | 20 - .../BasicImports_DesignTime.mappings.txt | 0 .../BasicImports_Imports0.cshtml | 5 - .../BasicImports_Imports1.cshtml | 2 - .../BasicImports_Runtime.codegen.cs | 36 - .../BasicImports_Runtime.ir.txt | 16 - .../CompilationFailedException.cs | 58 ++ .../IntegrationTests/CompiledAssembly.cs | 24 + .../IntegrationTests/CompiledCSharpCode.cs | 21 + .../IntegrationTests/IntegrationTestBase.cs | 411 +++++++-- ...rosoft.AspNetCore.Razor.Test.Common.csproj | 1 + 26 files changed, 1134 insertions(+), 772 deletions(-) delete mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Imports0.cshtml delete mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Imports0.cshtml delete mode 100644 test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports.cshtml delete mode 100644 test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_DesignTime.codegen.cs delete mode 100644 test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_DesignTime.ir.txt delete mode 100644 test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_DesignTime.mappings.txt delete mode 100644 test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports0.cshtml delete mode 100644 test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports1.cshtml delete mode 100644 test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Runtime.codegen.cs delete mode 100644 test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Runtime.ir.txt create mode 100644 test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/CompilationFailedException.cs create mode 100644 test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/CompiledAssembly.cs create mode 100644 test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/CompiledCSharpCode.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs index 87ea8b68a4..b36e2dcad9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -1,61 +1,97 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text.RegularExpressions; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.IntegrationTests; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Razor; using Xunit; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.IntegrationTests { public class CodeGenerationIntegrationTest : IntegrationTestBase { - private static readonly RazorSourceDocument DefaultImports = MvcRazorTemplateEngine.GetDefaultImports(); + private readonly static CSharpCompilation DefaultBaseCompilation = MvcShim.BaseCompilation.WithAssemblyName("AppCode"); - private CSharpCompilation BaseCompilation => MvcShim.BaseCompilation.WithAssemblyName("AppCode"); + public CodeGenerationIntegrationTest() + : base(generateBaselines: null) + { + Configuration = RazorConfiguration.Create( + RazorLanguageVersion.Version_2_0, + "MVC-2.1", + new[] { new AssemblyExtension("MVC-2.1", typeof(ExtensionInitializer).Assembly) }); + } + + protected override CSharpCompilation BaseCompilation => DefaultBaseCompilation; + + protected override RazorConfiguration Configuration { get; } #region Runtime [Fact] public void UsingDirectives_Runtime() { - var compilation = BaseCompilation; + // Arrange + var projectItem = CreateProjectItemFromFile(); - RunRuntimeTest(compilation, new[] { "The using directive for 'System' appeared previously in this namespace" }); + // Act + var compiled = CompileToAssembly(projectItem, designTime: false, throwOnFailure: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + + var diagnostics = compiled.Compilation.GetDiagnostics().Where(d => d.Severity >= DiagnosticSeverity.Warning); + Assert.Equal("The using directive for 'System' appeared previously in this namespace", Assert.Single(diagnostics).GetMessage()); } [Fact] public void InvalidNamespaceAtEOF_Runtime() { - var compilation = BaseCompilation; - RunRuntimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToCSharp(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + + var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; + Assert.Equal("RZ1014", Assert.Single(diagnotics).Id); } [Fact] public void IncompleteDirectives_Runtime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class MyService { public string Html { get; set; } -}"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +}"); - RunRuntimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToCSharp(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + + // We expect this test to generate a bunch of errors. + Assert.True(compiled.CodeDocument.GetCSharpDocument().Diagnostics.Count > 0); } [Fact] public void InheritsViewModel_Runtime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Razor; @@ -70,16 +106,23 @@ public class MyModel { } -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +"); - RunRuntimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void InheritsWithViewImports_Runtime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.RazorPages; @@ -94,71 +137,113 @@ public abstract class MyPageModel : Page public class MyModel { -}"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +}"); + AddProjectItemFromText(@"@inherits MyPageModel"); - RunRuntimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void MalformedPageDirective_Runtime() { - var compilation = BaseCompilation; + // Arrange + var projectItem = CreateProjectItemFromFile(); - RunRuntimeTest(compilation); + // Act + var compiled = CompileToCSharp(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + + var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; + Assert.Equal("RZ1016", Assert.Single(diagnotics).Id); } [Fact] public void Basic_Runtime() { - var compilation = BaseCompilation; + // Arrange + var projectItem = CreateProjectItemFromFile(); - RunRuntimeTest(compilation); + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void Sections_Runtime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" using Microsoft.AspNetCore.Mvc.ViewFeatures; public class InputTestTagHelper : {typeof(TagHelper).FullName} {{ public ModelExpression For {{ get; set; }} }} -"; +"); - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); + var projectItem = CreateProjectItemFromFile(); - RunRuntimeTest(compilation); + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void _ViewImports_Runtime() { - var compilation = BaseCompilation; + // Arrange + var projectItem = CreateProjectItemFromFile(); - RunRuntimeTest(compilation); + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void Inject_Runtime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class MyApp { public string MyProperty { get; set; } } -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +"); - RunRuntimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void InjectWithModel_Runtime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class MyModel { @@ -172,16 +257,23 @@ public class MyService public class MyApp { public string MyProperty { get; set; } -}"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +}"); - RunRuntimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void InjectWithSemicolon_Runtime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class MyModel { @@ -196,90 +288,145 @@ public class MyService { public string Html { get; set; } } -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +"); - RunRuntimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void Model_Runtime() { - var compilation = BaseCompilation; + // Arrange + var projectItem = CreateProjectItemFromFile(); - RunRuntimeTest(compilation); + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void ModelExpressionTagHelper_Runtime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" using Microsoft.AspNetCore.Mvc.ViewFeatures; public class InputTestTagHelper : {typeof(TagHelper).FullName} {{ public ModelExpression For {{ get; set; }} }} -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +"); - RunRuntimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void RazorPages_Runtime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" public class DivTagHelper : {typeof(TagHelper).FullName} {{ }} -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +"); - RunRuntimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void RazorPagesWithRouteTemplate_Runtime() { - var compilation = BaseCompilation; + // Arrange + var projectItem = CreateProjectItemFromFile(); - RunRuntimeTest(compilation); + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void RazorPagesWithoutModel_Runtime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" public class DivTagHelper : {typeof(TagHelper).FullName} {{ }} -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +"); - RunRuntimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void PageWithNamespace_Runtime() { - var compilation = BaseCompilation; - RunRuntimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void ViewWithNamespace_Runtime() { - var compilation = BaseCompilation; - RunRuntimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void ViewComponentTagHelper_Runtime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" public class TestViewComponent {{ public string Invoke(string firstName) @@ -293,19 +440,33 @@ public class AllTagHelper : {typeof(TagHelper).FullName} {{ public string Bar {{ get; set; }} }} -"; +"); - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); - - RunRuntimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); } [Fact] public void RazorPageWithNoLeadingPageDirective_Runtime() { - var compilation = BaseCompilation; + // Arrange + var projectItem = CreateProjectItemFromFile(); - RunRuntimeTest(compilation); + // Act + var compiled = CompileToCSharp(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + + var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; + Assert.Equal("RZ3906", Assert.Single(diagnotics).Id); } #endregion @@ -314,37 +475,68 @@ public class AllTagHelper : {typeof(TagHelper).FullName} [Fact] public void UsingDirectives_DesignTime() { - var compilation = BaseCompilation; + // Arrange + var projectItem = CreateProjectItemFromFile(); - RunDesignTimeTest(compilation, new[] { "The using directive for 'System' appeared previously in this namespace" }); + // Act + var compiled = CompileToAssembly(projectItem, designTime: true, throwOnFailure: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + + var diagnostics = compiled.Compilation.GetDiagnostics().Where(d => d.Severity >= DiagnosticSeverity.Warning); + Assert.Equal("The using directive for 'System' appeared previously in this namespace", Assert.Single(diagnostics).GetMessage()); } [Fact] public void InvalidNamespaceAtEOF_DesignTime() { - var compilation = BaseCompilation; - RunDesignTimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToCSharp(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + + var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; + Assert.Equal("RZ1014", Assert.Single(diagnotics).Id); } [Fact] public void IncompleteDirectives_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class MyService { public string Html { get; set; } -} -"; +}"); - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); + var projectItem = CreateProjectItemFromFile(); - RunDesignTimeTest(compilation); + // Act + var compiled = CompileToCSharp(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + + // We expect this test to generate a bunch of errors. + Assert.True(compiled.CodeDocument.GetCSharpDocument().Diagnostics.Count > 0); } [Fact] public void InheritsViewModel_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Razor; @@ -355,29 +547,31 @@ public class MyBasePageForViews : RazorPage throw new System.NotImplementedException(); } } - public class MyModel { } -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +"); - RunDesignTimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void InheritsWithViewImports_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.RazorPages; -public class MyModel -{ - -} - public abstract class MyPageModel : Page { public override Task ExecuteAsync() @@ -385,69 +579,124 @@ public abstract class MyPageModel : Page throw new System.NotImplementedException(); } } -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +public class MyModel +{ - RunDesignTimeTest(compilation); +}"); + + AddProjectItemFromText(@"@inherits MyPageModel"); + + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void MalformedPageDirective_DesignTime() { - var compilation = BaseCompilation; - RunDesignTimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToCSharp(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + + var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; + Assert.Equal("RZ1016", Assert.Single(diagnotics).Id); } [Fact] public void Basic_DesignTime() { - var compilation = BaseCompilation; - RunDesignTimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void Sections_DesignTime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" using Microsoft.AspNetCore.Mvc.ViewFeatures; public class InputTestTagHelper : {typeof(TagHelper).FullName} {{ public ModelExpression For {{ get; set; }} }} -"; +"); - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); + var projectItem = CreateProjectItemFromFile(); - RunDesignTimeTest(compilation); + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void _ViewImports_DesignTime() { - var compilation = BaseCompilation; - RunDesignTimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void Inject_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class MyApp { public string MyProperty { get; set; } } -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +"); - RunDesignTimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void InjectWithModel_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class MyModel { @@ -461,129 +710,208 @@ public class MyService public class MyApp { public string MyProperty { get; set; } -} -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +}"); - RunDesignTimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void InjectWithSemicolon_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class MyModel { } -public class MyService -{ - public string Html { get; set; } -} - public class MyApp { public string MyProperty { get; set; } } -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); - RunDesignTimeTest(compilation); +public class MyService +{ + public string Html { get; set; } +} +"); + + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void Model_DesignTime() { - var compilation = BaseCompilation; - RunDesignTimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void MultipleModels_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class ThisShouldBeGenerated { -}"; +}"); - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); + var projectItem = CreateProjectItemFromFile(); - RunDesignTimeTest(compilation); + // Act + var compiled = CompileToCSharp(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + + var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; + Assert.Equal("RZ2001", Assert.Single(diagnotics).Id); } [Fact] public void ModelExpressionTagHelper_DesignTime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" using Microsoft.AspNetCore.Mvc.ViewFeatures; public class InputTestTagHelper : {typeof(TagHelper).FullName} {{ public ModelExpression For {{ get; set; }} }} -"; +"); - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); + var projectItem = CreateProjectItemFromFile(); - RunDesignTimeTest(compilation); + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void RazorPages_DesignTime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" public class DivTagHelper : {typeof(TagHelper).FullName} {{ }} -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +"); - RunDesignTimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void RazorPagesWithRouteTemplate_DesignTime() { - var compilation = BaseCompilation; + // Arrange + var projectItem = CreateProjectItemFromFile(); - RunDesignTimeTest(compilation); + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void RazorPagesWithoutModel_DesignTime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" public class DivTagHelper : {typeof(TagHelper).FullName} {{ }} -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +"); - RunDesignTimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void PageWithNamespace_DesignTime() { - var compilation = BaseCompilation; - RunDesignTimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void ViewWithNamespace_DesignTime() { - var compilation = BaseCompilation; - RunDesignTimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void ViewComponentTagHelper_DesignTime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" public class TestViewComponent {{ public string Invoke(string firstName) @@ -597,153 +925,37 @@ public class AllTagHelper : {typeof(TagHelper).FullName} {{ public string Bar {{ get; set; }} }} -"; +"); - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); - - RunDesignTimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void RazorPageWithNoLeadingPageDirective_DesignTime() { - var compilation = BaseCompilation; + // Arrange + var projectItem = CreateProjectItemFromFile(); - RunDesignTimeTest(compilation); + // Act + var compiled = CompileToCSharp(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + + var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; + Assert.Equal("RZ3906", Assert.Single(diagnotics).Id); } + #endregion - - private void RunRuntimeTest( - CSharpCompilation baseCompilation, - IEnumerable expectedWarnings = null) - { - Assert.Empty(baseCompilation.GetDiagnostics()); - - // Arrange - var engine = CreateEngine(baseCompilation); - var projectItem = CreateProjectItem(); - - // Act - var document = engine.Process(projectItem); - - // Assert - AssertDocumentNodeMatchesBaseline(document.GetDocumentIntermediateNode()); - AssertCSharpDocumentMatchesBaseline(document.GetCSharpDocument()); - AssertDocumentCompiles(document, baseCompilation, expectedWarnings); - } - - private void RunDesignTimeTest( - CSharpCompilation baseCompilation, - IEnumerable expectedWarnings = null) - { - Assert.Empty(baseCompilation.GetDiagnostics()); - - // Arrange - var engine = CreateEngine(baseCompilation); - var projectItem = CreateProjectItem(); - - // Act - var document = engine.ProcessDesignTime(projectItem); - - // Assert - AssertDocumentNodeMatchesBaseline(document.GetDocumentIntermediateNode()); - AssertCSharpDocumentMatchesBaseline(document.GetCSharpDocument()); - AssertSourceMappingsMatchBaseline(document); - AssertDocumentCompiles(document, baseCompilation, expectedWarnings); - } - - private void AssertDocumentCompiles( - RazorCodeDocument document, - CSharpCompilation baseCompilation, - IEnumerable expectedWarnings = null) - { - var cSharp = document.GetCSharpDocument().GeneratedCode; - - var syntaxTree = CSharpSyntaxTree.ParseText(cSharp); - var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); - - var references = baseCompilation.References.Concat(new[] { baseCompilation.ToMetadataReference() }); - var compilation = CSharpCompilation.Create("CodeGenerationTestAssembly", new[] { syntaxTree }, references, options); - - var diagnostics = compilation.GetDiagnostics(); - - var warnings = diagnostics.Where(d => d.Severity >= DiagnosticSeverity.Warning); - - if (expectedWarnings == null) - { - Assert.Empty(warnings); - } - else - { - Assert.Equal(expectedWarnings, warnings.Select(e => e.GetMessage())); - } - } - - protected RazorProjectEngine CreateEngine(CSharpCompilation compilation) - { - var references = compilation.References.Concat(new[] { compilation.ToMetadataReference() }); - - return CreateProjectEngine(b => - { - RazorExtensions.Register(b); - - var existingImportFeature = b.Features.OfType().Single(); - b.SetImportFeature(new NormalizedDefaultImportFeature(existingImportFeature)); - - b.Features.Add(GetMetadataReferenceFeature(references)); - b.Features.Add(new CompilationTagHelperFeature()); - }); - } - - private static IRazorEngineFeature GetMetadataReferenceFeature(IEnumerable references) - { - return new DefaultMetadataReferenceFeature() - { - References = references.ToList() - }; - } - - private class NormalizedDefaultImportFeature : RazorProjectEngineFeatureBase, IImportProjectFeature - { - private IImportProjectFeature _existingFeature; - - public NormalizedDefaultImportFeature(IImportProjectFeature existingFeature) - { - _existingFeature = existingFeature; - } - - protected override void OnInitialized() - { - _existingFeature.ProjectEngine = ProjectEngine; - } - - public IReadOnlyList GetImports(RazorProjectItem projectItem) - { - var normalizedImports = new List(); - var imports = _existingFeature.GetImports(projectItem); - foreach (var import in imports) - { - var text = string.Empty; - using (var stream = import.Read()) - using (var reader = new StreamReader(stream)) - { - text = reader.ReadToEnd().Trim(); - } - - // It's important that we normalize the newlines in the default imports. The default imports will - // be created with Environment.NewLine, but we need to normalize to `\r\n` so that the indices - // are the same on xplat. - var normalizedText = Regex.Replace(text, "(? - DesignTimeDirective - - DirectiveToken - (10:0,10 [19] InheritsWithViewImports_Imports0.cshtml) - MyPageModel DirectiveToken - (231:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper DirectiveToken - (294:7,71 [4] ) - Html DirectiveToken - (308:8,8 [54] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper @@ -24,6 +23,7 @@ Document - DirectiveToken - (617:12,14 [96] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor DirectiveToken - (729:13,14 [87] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor DirectiveToken - (832:14,14 [87] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (10:0,10 [19] TestFiles\IntegrationTests\CodeGenerationIntegrationTest\_ViewImports.cshtml) - MyPageModel DirectiveToken - (14:1,7 [7] InheritsWithViewImports.cshtml) - MyModel CSharpCode - IntermediateToken - - CSharp - #pragma warning disable 0414 diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Imports0.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Imports0.cshtml deleted file mode 100644 index fdc08de01d..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Imports0.cshtml +++ /dev/null @@ -1 +0,0 @@ -@inherits MyPageModel diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Runtime.codegen.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Runtime.codegen.cs index 00a6cb3a1d..c1ea0b5c3e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Runtime.codegen.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Runtime.codegen.cs @@ -14,7 +14,7 @@ namespace AspNetCore using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"d196fc1c66d46d35e35af9b01c737e12bcce6782", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports.cshtml")] - [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"30a9e14edcefbf4970c45de29d8870cf1a9b90b5", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Imports0.cshtml")] + [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"f311ecbb5c4d63980a59c24af5ffe8baa1c3f99a", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/_ViewImports.cshtml")] public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InheritsWithViewImports : MyPageModel { #pragma warning disable 1998 diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/IntegrationTests/CodeGenerationIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/IntegrationTests/CodeGenerationIntegrationTest.cs index 27b93fd264..bb605fed72 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -1,55 +1,78 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.IntegrationTests; using Microsoft.AspNetCore.Razor.TagHelpers; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Emit; -using Microsoft.CodeAnalysis.Razor; -using Microsoft.Extensions.DependencyModel; using Xunit; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.IntegrationTests { public class CodeGenerationIntegrationTest : IntegrationTestBase { - private static readonly RazorSourceDocument DefaultImports = MvcRazorTemplateEngine.GetDefaultImports(); + private readonly static CSharpCompilation DefaultBaseCompilation = MvcShim.BaseCompilation.WithAssemblyName("AppCode"); - private CSharpCompilation BaseCompilation => MvcShim.BaseCompilation.WithAssemblyName("AppCode"); + public CodeGenerationIntegrationTest() + : base(generateBaselines: null) + { + Configuration = RazorConfiguration.Create( + RazorLanguageVersion.Version_1_1, + "MVC-1.1", + new[] { new AssemblyExtension("MVC-1.1", typeof(ExtensionInitializer).Assembly) }); + } + + protected override CSharpCompilation BaseCompilation => DefaultBaseCompilation; + + protected override RazorConfiguration Configuration { get; } [Fact] public void InvalidNamespaceAtEOF_DesignTime() { - var compilation = BaseCompilation; - RunDesignTimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToCSharp(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + + var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; + Assert.Equal("RZ1007", Assert.Single(diagnotics).Id); } [Fact] public void IncompleteDirectives_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class MyService { public string Html { get; set; } -} -"; +}"); - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); - RunDesignTimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToCSharp(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + + // We expect this test to generate a bunch of errors. + Assert.True(compiled.CodeDocument.GetCSharpDocument().Diagnostics.Count > 0); } [Fact] public void InheritsViewModel_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Razor; @@ -60,20 +83,28 @@ public class MyBasePageForViews : RazorPage throw new System.NotImplementedException(); } } - public class MyModel { } -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); - RunDesignTimeTest(compilation); +"); + + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void InheritsWithViewImports_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Razor; @@ -88,61 +119,102 @@ public class MyBasePageForViews : RazorPage public class MyModel { -} -"; +}"); - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); - RunDesignTimeTest(compilation); + AddProjectItemFromText(@"@inherits MyBasePageForViews"); + + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void Basic_DesignTime() { - var compilation = BaseCompilation; - RunDesignTimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void Sections_DesignTime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" using Microsoft.AspNetCore.Mvc.ViewFeatures; public class InputTestTagHelper : {typeof(TagHelper).FullName} {{ public ModelExpression For {{ get; set; }} }} -"; +"); - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); + var projectItem = CreateProjectItemFromFile(); - RunDesignTimeTest(compilation); + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void _ViewImports_DesignTime() { - var compilation = BaseCompilation; - RunDesignTimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void Inject_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class MyApp { public string MyProperty { get; set; } } -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); - RunDesignTimeTest(compilation); +"); + + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void InjectWithModel_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class MyModel { @@ -156,75 +228,119 @@ public class MyService public class MyApp { public string MyProperty { get; set; } -} -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); - RunDesignTimeTest(compilation); +}"); + + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void InjectWithSemicolon_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class MyModel { } -public class MyService -{ - public string Html { get; set; } -} - public class MyApp { public string MyProperty { get; set; } } -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); - RunDesignTimeTest(compilation); + +public class MyService +{ + public string Html { get; set; } +} +"); + + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void Model_DesignTime() { - var compilation = BaseCompilation; - RunDesignTimeTest(compilation); + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void MultipleModels_DesignTime() { - var appCode = @" + // Arrange + AddCSharpSyntaxTree(@" public class ThisShouldBeGenerated { -}"; +}"); - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); - RunDesignTimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToCSharp(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + + var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; + Assert.Equal("RZ2001", Assert.Single(diagnotics).Id); } [Fact] public void ModelExpressionTagHelper_DesignTime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" using Microsoft.AspNetCore.Mvc.ViewFeatures; public class InputTestTagHelper : {typeof(TagHelper).FullName} {{ public ModelExpression For {{ get; set; }} }} -"; - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); +"); - RunDesignTimeTest(compilation); + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] public void ViewComponentTagHelper_DesignTime() { - var appCode = $@" + // Arrange + AddCSharpSyntaxTree($@" public class TestViewComponent {{ public string Invoke(string firstName) @@ -238,126 +354,17 @@ public class AllTagHelper : {typeof(TagHelper).FullName} {{ public string Bar {{ get; set; }} }} -"; +"); - var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode)); - - RunDesignTimeTest(compilation); - } - - private void RunDesignTimeTest( - CSharpCompilation baseCompilation, - IEnumerable expectedErrors = null) - { - Assert.Empty(baseCompilation.GetDiagnostics()); - - // Arrange - var engine = CreateEngine(baseCompilation); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); // Act - var document = engine.ProcessDesignTime(projectItem); + var compiled = CompileToAssembly(projectItem, designTime: true); // Assert - AssertDocumentNodeMatchesBaseline(document.GetDocumentIntermediateNode()); - AssertCSharpDocumentMatchesBaseline(document.GetCSharpDocument()); - AssertSourceMappingsMatchBaseline(document); - AssertDocumentCompiles(document, baseCompilation, expectedErrors); - } - - private void AssertDocumentCompiles( - RazorCodeDocument document, - CSharpCompilation baseCompilation, - IEnumerable expectedErrors = null) - { - var cSharp = document.GetCSharpDocument().GeneratedCode; - - var syntaxTree = CSharpSyntaxTree.ParseText(cSharp); - var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); - - var references = baseCompilation.References.Concat(new[] { baseCompilation.ToMetadataReference() }); - var compilation = CSharpCompilation.Create("CodeGenerationTestAssembly", new[] { syntaxTree }, references, options); - - var diagnostics = compilation.GetDiagnostics(); - - var errors = diagnostics.Where(d => d.Severity >= DiagnosticSeverity.Warning); - - if (expectedErrors == null) - { - Assert.Empty(errors.Select(e => e.GetMessage())); - } - else - { - Assert.Equal(expectedErrors, errors.Select(e => e.GetMessage())); - } - } - - protected RazorProjectEngine CreateEngine(CSharpCompilation compilation) - { - var references = compilation.References.Concat(new[] { compilation.ToMetadataReference() }); - - return CreateProjectEngine(b => - { - RazorExtensions.Register(b); - RazorExtensions.RegisterViewComponentTagHelpers(b); - - var existingImportFeature = b.Features.OfType().Single(); - b.SetImportFeature(new NormalizedDefaultImportFeature(existingImportFeature)); - - b.Features.Add(GetMetadataReferenceFeature(references)); - b.Features.Add(new CompilationTagHelperFeature()); - }); - } - - private static IRazorEngineFeature GetMetadataReferenceFeature(IEnumerable references) - { - return new DefaultMetadataReferenceFeature() - { - References = references.ToList() - }; - } - - private class NormalizedDefaultImportFeature : RazorProjectEngineFeatureBase, IImportProjectFeature - { - private IImportProjectFeature _existingFeature; - - public NormalizedDefaultImportFeature(IImportProjectFeature existingFeature) - { - _existingFeature = existingFeature; - } - - protected override void OnInitialized() - { - _existingFeature.ProjectEngine = ProjectEngine; - } - - public IReadOnlyList GetImports(RazorProjectItem projectItem) - { - var normalizedImports = new List(); - var imports = _existingFeature.GetImports(projectItem); - foreach (var import in imports) - { - var text = string.Empty; - using (var stream = import.Read()) - using (var reader = new StreamReader(stream)) - { - text = reader.ReadToEnd().Trim(); - } - - // It's important that we normalize the newlines in the default imports. The default imports will - // be created with Environment.NewLine, but we need to normalize to `\r\n` so that the indices - // are the same on xplat. - var normalizedText = Regex.Replace(text, "(? - DesignTimeDirective - - DirectiveToken - (10:0,10 [26] InheritsWithViewImports_Imports0.cshtml) - MyBasePageForViews DirectiveToken - (231:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper DirectiveToken - (294:7,71 [4] ) - Html DirectiveToken - (308:8,8 [54] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper @@ -22,6 +21,7 @@ Document - DirectiveToken - (507:11,8 [70] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider DirectiveToken - (578:11,79 [23] ) - ModelExpressionProvider DirectiveToken - (617:12,14 [96] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (10:0,10 [26] TestFiles\IntegrationTests\CodeGenerationIntegrationTest\_ViewImports.cshtml) - MyBasePageForViews DirectiveToken - (7:0,7 [7] InheritsWithViewImports.cshtml) - MyModel CSharpCode - IntermediateToken - - CSharp - #pragma warning disable 0414 diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Imports0.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Imports0.cshtml deleted file mode 100644 index 4c14f15bad..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InheritsWithViewImports_Imports0.cshtml +++ /dev/null @@ -1 +0,0 @@ -@inherits MyBasePageForViews diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/BasicIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/BasicIntegrationTest.cs index 4f9facbdfd..9ae793d5a1 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/BasicIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/BasicIntegrationTest.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests { // Arrange var projectEngine = CreateProjectEngine(); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); // Act var codeDocument = projectEngine.Process(projectItem); @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests { // Arrange var projectEngine = CreateProjectEngine(); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); // Act var codeDocument = projectEngine.Process(projectItem); @@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests b.AddDirective(DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine)); }); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); // Act var codeDocument = projectEngine.Process(projectItem); @@ -53,65 +53,5 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests // Assert AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode()); } - - [Fact] - public void BuildEngine_CallProcess() - { - // Arrange - var projectEngine = CreateProjectEngine(); - var projectItem = new TestRazorProjectItem("Index.cshtml"); - - // Act - var codeDocument = projectEngine.Process(projectItem); - - // Assert - Assert.NotNull(codeDocument.GetSyntaxTree()); - Assert.NotNull(codeDocument.GetDocumentIntermediateNode()); - } - - [Fact] - public void CSharpDocument_Runtime_PreservesParserErrors() - { - // Arrange - var projectEngine = CreateProjectEngine(); - var projectItem = new TestRazorProjectItem("test.cshtml") - { - Content = "@!!!" - }; - - var expected = RazorDiagnosticFactory.CreateParsing_UnexpectedCharacterAtStartOfCodeBlock( - new SourceSpan(new SourceLocation("test.cshtml", 1, 0, 1), contentLength: 1), - "!"); - - // Act - var codeDocument = projectEngine.Process(projectItem); - - // Assert - var csharpDocument = codeDocument.GetCSharpDocument(); - var error = Assert.Single(csharpDocument.Diagnostics); - Assert.Equal(expected, error); - } - - [Fact] - public void CSharpDocument_DesignTime_PreservesParserErrors() - { - // Arrange - var projectEngine = CreateProjectEngine(); - var projectItem = new TestRazorProjectItem("test.cshtml") - { - Content = "@{" - }; - - var expected = RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF( - new SourceSpan(new SourceLocation("test.cshtml", 1, 0, 1), contentLength: 1), Resources.BlockName_Code, "}", "{"); - - // Act - var codeDocument = projectEngine.ProcessDesignTime(projectItem); - - // Assert - var csharpDocument = codeDocument.GetCSharpDocument(); - var error = Assert.Single(csharpDocument.Diagnostics); - Assert.Equal(expected, error); - } } } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/CodeGenerationIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/CodeGenerationIntegrationTest.cs index 34ddc3fb94..6ead22dfae 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests { public class CodeGenerationIntegrationTest : IntegrationTestBase { + #region Runtime [Fact] public void IncompleteDirectives_Runtime() @@ -23,12 +24,6 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests RunTimeTest(); } - [Fact] - public void BasicImports_Runtime() - { - RunTimeTest(); - } - [Fact] public void UnfinishedExpressionInCode_Runtime() { @@ -460,12 +455,6 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests DesignTimeTest(); } - [Fact] - public void BasicImports_DesignTime() - { - DesignTimeTest(); - } - [Fact] public void UnfinishedExpressionInCode_DesignTime() { @@ -896,7 +885,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests SectionDirective.Register(builder); }); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); // Act var codeDocument = projectEngine.ProcessDesignTime(projectItem); @@ -922,7 +911,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests SectionDirective.Register(builder); }); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); // Act var codeDocument = projectEngine.Process(projectItem); @@ -947,7 +936,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests SectionDirective.Register(builder); }); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); var imports = GetImports(projectEngine, projectItem); // Act @@ -973,7 +962,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests SectionDirective.Register(builder); }); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); var imports = GetImports(projectEngine, projectItem); // Act diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/ExtensibleDirectiveTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/ExtensibleDirectiveTest.cs index 9e722b53b9..e26ce3b6fd 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/ExtensibleDirectiveTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/ExtensibleDirectiveTest.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests builder.AddDirective(DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddNamespaceToken())); }); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); // Act var codeDocument = engine.ProcessDesignTime(projectItem); diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/HtmlAttributeIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/HtmlAttributeIntegrationTest.cs index 7683ca94ae..7fd26ffd80 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/HtmlAttributeIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/HtmlAttributeIntegrationTest.cs @@ -11,28 +11,26 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests public void HtmlWithDataDashAttribute() { // Arrange - var projectEngine = CreateProjectEngine(); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); // Act - var codeDocument = projectEngine.Process(projectItem); + var compiled = CompileToCSharp(projectItem); // Assert - AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode()); + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); } [Fact] public void HtmlWithConditionalAttribute() { // Arrange - var projectEngine = CreateProjectEngine(); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); // Act - var codeDocument = projectEngine.Process(projectItem); + var compiled = CompileToCSharp(projectItem); // Assert - AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode()); + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode()); } } } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/TagHelpersIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/TagHelpersIntegrationTest.cs index 7d7ce390ec..c8cd0e4146 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/TagHelpersIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/TagHelpersIntegrationTest.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests }; var projectEngine = CreateProjectEngine(builder => builder.AddTagHelpers(descriptors)); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); // Act var codeDocument = projectEngine.Process(projectItem); @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests }; var projectEngine = CreateProjectEngine(builder => builder.AddTagHelpers(descriptors)); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); // Act var codeDocument = projectEngine.Process(projectItem); @@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests }; var projectEngine = CreateProjectEngine(builder => builder.AddTagHelpers(descriptors)); - var projectItem = CreateProjectItem(); + var projectItem = CreateProjectItemFromFile(); // Act var codeDocument = projectEngine.Process(projectItem); diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports.cshtml b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports.cshtml deleted file mode 100644 index bf4d925aea..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports.cshtml +++ /dev/null @@ -1 +0,0 @@ -

Hi there!

diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_DesignTime.codegen.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_DesignTime.codegen.cs deleted file mode 100644 index b6226ce38a..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_DesignTime.codegen.cs +++ /dev/null @@ -1,37 +0,0 @@ -// -#pragma warning disable 1591 -namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests.TestFiles -{ - #line hidden -#line 1 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports0.cshtml" -using System.Globalization; - -#line default -#line hidden -#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports0.cshtml" -using System.ComponentModel; - -#line default -#line hidden -#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports1.cshtml" -using System.Text; - -#line default -#line hidden - public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_BasicImports_DesignTime : Hello - { - #pragma warning disable 219 - private void __RazorDirectiveTokenHelpers__() { - } - #pragma warning restore 219 - #pragma warning disable 0414 - private static System.Object __o = null; - #pragma warning restore 0414 - #pragma warning disable 1998 - public async System.Threading.Tasks.Task ExecuteAsync() - { - } - #pragma warning restore 1998 - } -} -#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_DesignTime.ir.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_DesignTime.ir.txt deleted file mode 100644 index fab53bac03..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_DesignTime.ir.txt +++ /dev/null @@ -1,20 +0,0 @@ -Document - - NamespaceDeclaration - - Microsoft.AspNetCore.Razor.Language.IntegrationTests.TestFiles - UsingDirective - (1:0,1 [26] BasicImports_Imports0.cshtml) - System.Globalization - UsingDirective - (30:1,1 [27] BasicImports_Imports0.cshtml) - System.ComponentModel - UsingDirective - (23:1,1 [18] BasicImports_Imports1.cshtml) - System.Text - ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_BasicImports_DesignTime - Hello - - DesignTimeDirective - - DirectiveToken - (69:2,10 [5] BasicImports_Imports0.cshtml) - Hello - CSharpCode - - IntermediateToken - - CSharp - #pragma warning disable 0414 - CSharpCode - - IntermediateToken - - CSharp - private static System.Object __o = null; - CSharpCode - - IntermediateToken - - CSharp - #pragma warning restore 0414 - MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync - HtmlContent - (0:0,0 [18] BasicImports.cshtml) - IntermediateToken - (0:0,0 [3] BasicImports.cshtml) - Html -

- IntermediateToken - (3:0,3 [9] BasicImports.cshtml) - Html - Hi there! - IntermediateToken - (12:0,12 [4] BasicImports.cshtml) - Html -

- IntermediateToken - (16:0,16 [2] BasicImports.cshtml) - Html - \n diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_DesignTime.mappings.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_DesignTime.mappings.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports0.cshtml b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports0.cshtml deleted file mode 100644 index b71f569138..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports0.cshtml +++ /dev/null @@ -1,5 +0,0 @@ -@using System.Globalization -@using System.ComponentModel -@inherits Hello -@("And also this") -

This will get ignored

diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports1.cshtml b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports1.cshtml deleted file mode 100644 index bac4a38695..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports1.cshtml +++ /dev/null @@ -1,2 +0,0 @@ -@section ignored { } -@using System.Text; diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Runtime.codegen.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Runtime.codegen.cs deleted file mode 100644 index ed71849611..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Runtime.codegen.cs +++ /dev/null @@ -1,36 +0,0 @@ -#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "d5e5d28c0c504a7b0c5207a5230a5b7327ce5e09" -// -#pragma warning disable 1591 -[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(Microsoft.AspNetCore.Razor.Language.IntegrationTests.TestFiles.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_BasicImports_Runtime), @"default", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports.cshtml")] -namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests.TestFiles -{ - #line hidden -#line 1 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports0.cshtml" -using System.Globalization; - -#line default -#line hidden -#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports0.cshtml" -using System.ComponentModel; - -#line default -#line hidden -#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports1.cshtml" -using System.Text; - -#line default -#line hidden - [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"d5e5d28c0c504a7b0c5207a5230a5b7327ce5e09", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports.cshtml")] - [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"39c22940acc420eb6d46b6ff85ef6d5a2ab35fa1", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports0.cshtml")] - [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"ede697b6fb90aac312d589ae96a93289cdeb506f", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Imports1.cshtml")] - public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_BasicImports_Runtime : Hello - { - #pragma warning disable 1998 - public async System.Threading.Tasks.Task ExecuteAsync() - { - WriteLiteral("

Hi there!

\r\n"); - } - #pragma warning restore 1998 - } -} -#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Runtime.ir.txt b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Runtime.ir.txt deleted file mode 100644 index 014ca50451..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports_Runtime.ir.txt +++ /dev/null @@ -1,16 +0,0 @@ -Document - - RazorCompiledItemAttribute - - NamespaceDeclaration - - Microsoft.AspNetCore.Razor.Language.IntegrationTests.TestFiles - UsingDirective - (1:0,1 [28] BasicImports_Imports0.cshtml) - System.Globalization - UsingDirective - (30:1,1 [29] BasicImports_Imports0.cshtml) - System.ComponentModel - UsingDirective - (23:1,1 [20] BasicImports_Imports1.cshtml) - System.Text - RazorSourceChecksumAttribute - - RazorSourceChecksumAttribute - - RazorSourceChecksumAttribute - - ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_BasicImports_Runtime - Hello - - MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync - HtmlContent - (0:0,0 [18] BasicImports.cshtml) - IntermediateToken - (0:0,0 [3] BasicImports.cshtml) - Html -

- IntermediateToken - (3:0,3 [9] BasicImports.cshtml) - Html - Hi there! - IntermediateToken - (12:0,12 [4] BasicImports.cshtml) - Html -

- IntermediateToken - (16:0,16 [2] BasicImports.cshtml) - Html - \n diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/CompilationFailedException.cs b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/CompilationFailedException.cs new file mode 100644 index 0000000000..febd3f69c6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/CompilationFailedException.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests +{ + public class CompilationFailedException : XunitException + { + public CompilationFailedException(Compilation compilation, Diagnostic[] diagnostics) + { + Compilation = compilation; + Diagnostics = diagnostics; + } + + public Compilation Compilation { get; } + + public Diagnostic[] Diagnostics { get; } + + public override string Message + { + get + { + var builder = new StringBuilder(); + builder.AppendLine("Compilation failed: "); + + var syntaxTreesWithErrors = new HashSet(); + foreach (var diagnostic in Diagnostics) + { + builder.AppendLine(diagnostic.ToString()); + + if (diagnostic.Location.IsInSource) + { + syntaxTreesWithErrors.Add(diagnostic.Location.SourceTree); + } + } + + if (syntaxTreesWithErrors.Any()) + { + builder.AppendLine(); + builder.AppendLine(); + + foreach (var syntaxTree in syntaxTreesWithErrors) + { + builder.AppendLine($"File {syntaxTree.FilePath ?? "unknown"}:"); + builder.AppendLine(syntaxTree.GetText().ToString()); + } + } + + return builder.ToString(); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/CompiledAssembly.cs b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/CompiledAssembly.cs new file mode 100644 index 0000000000..4024de0f49 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/CompiledAssembly.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests +{ + public class CompiledAssembly + { + public CompiledAssembly(Compilation compilation, RazorCodeDocument codeDocument, Assembly assembly) + { + Compilation = compilation; + CodeDocument = codeDocument; + Assembly = assembly; + } + + public Assembly Assembly { get; } + + public RazorCodeDocument CodeDocument { get; } + + public Compilation Compilation { get; } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/CompiledCSharpCode.cs b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/CompiledCSharpCode.cs new file mode 100644 index 0000000000..9d10341428 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/CompiledCSharpCode.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests +{ + public class CompiledCSharpCode + { + public CompiledCSharpCode(Compilation baseCompilation, RazorCodeDocument codeDocument) + { + BaseCompilation = baseCompilation; + CodeDocument = codeDocument; + } + + // A compilation that can be used *with* this code to compile an assembly + public Compilation BaseCompilation { get; set; } + + public RazorCodeDocument CodeDocument { get; set; } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntegrationTestBase.cs b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntegrationTestBase.cs index c8c41c1f77..ffd7b849bb 100644 --- a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntegrationTestBase.cs +++ b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntegrationTestBase.cs @@ -18,6 +18,9 @@ using Microsoft.AspNetCore.Razor.Language.CodeGeneration; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Xunit; using Xunit.Sdk; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Razor; +using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests { @@ -28,15 +31,81 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests private static readonly AsyncLocal _fileName = new AsyncLocal(); #endif - protected IntegrationTestBase() + private static readonly CSharpCompilation DefaultBaseCompilation; + + static IntegrationTestBase() { - TestProjectRoot = TestProject.GetProjectDirectory(GetType()); + var referenceAssemblyRoots = new[] + { + typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly, // System.Runtime + }; + + var referenceAssemblies = referenceAssemblyRoots + .SelectMany(assembly => assembly.GetReferencedAssemblies().Concat(new[] { assembly.GetName() })) + .Distinct() + .Select(Assembly.Load) + .Select(assembly => MetadataReference.CreateFromFile(assembly.Location)) + .ToList(); + DefaultBaseCompilation = CSharpCompilation.Create( + "TestAssembly", + Array.Empty(), + referenceAssemblies, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); } + protected IntegrationTestBase(bool? generateBaselines = null) + { + TestProjectRoot = TestProject.GetProjectDirectory(GetType()); + + if (generateBaselines.HasValue) + { + GenerateBaselines = generateBaselines.Value; + } + } + + /// + /// Gets the that will be used as the 'app' compilation. + /// + protected virtual CSharpCompilation BaseCompilation => DefaultBaseCompilation; + + /// + /// Gets the parse options applied when using . + /// + protected virtual CSharpParseOptions CSharpParseOptions { get; } = new CSharpParseOptions(LanguageVersion.Latest); + + /// + /// Gets the compilation options applied when compiling assemblies. + /// + protected virtual CSharpCompilationOptions CSharpCompilationOptions { get; } = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + + /// + /// Gets a list of CSharp syntax trees used that are considered part of the 'app'. + /// + protected virtual List CSharpSyntaxTrees { get; } = new List(); + + /// + /// Gets the that will be used for code generation. + /// + protected virtual RazorConfiguration Configuration { get; } = RazorConfiguration.Default; + + protected virtual bool DesignTime { get; } = false; + + /// + /// Gets the + /// + internal VirtualRazorProjectFileSystem FileSystem { get; } = new VirtualRazorProjectFileSystem(); + + /// + /// Used to force a specific style of line-endings for testing. This matters for the baseline tests that exercise line mappings. + /// Even though we normalize newlines for testing, the difference between platforms affects the data through the *count* of + /// characters written. + /// + protected virtual string LineEnding { get; } = "\r\n"; + #if GENERATE_BASELINES - protected bool GenerateBaselines { get; set; } = true; + protected bool GenerateBaselines { get; } = true; #else - protected bool GenerateBaselines { get; set; } = false; + protected bool GenerateBaselines { get; } = false; #endif protected string TestProjectRoot { get; } @@ -60,34 +129,62 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests #endif } - protected virtual RazorProjectEngine CreateProjectEngine() => CreateProjectEngine(configure: null); - - protected virtual RazorProjectEngine CreateProjectEngine(Action configure) + protected virtual void ConfigureProjectEngine(RazorProjectEngineBuilder builder) { - if (FileName == null) - { - var message = $"{nameof(CreateProjectEngine)} should only be called from an integration test, ({nameof(FileName)} is null)."; - throw new InvalidOperationException(message); - } - - var assembly = GetType().GetTypeInfo().Assembly; - var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, IntegrationTestFileSystem.Default, b => - { - configure?.Invoke(b); - - var existingImportFeature = b.Features.OfType().Single(); - b.SetImportFeature(new IntegrationTestImportFeature(assembly, existingImportFeature)); - }); - var testProjectEngine = new IntegrationTestProjectEngine(projectEngine); - - return testProjectEngine; } - protected virtual RazorProjectItem CreateProjectItem() + protected CSharpSyntaxTree AddCSharpSyntaxTree(string text, string filePath = null) + { + var syntaxTree = (CSharpSyntaxTree)CSharpSyntaxTree.ParseText(text, CSharpParseOptions, path: filePath); + CSharpSyntaxTrees.Add(syntaxTree); + return syntaxTree; + } + + protected RazorProjectItem AddProjectItemFromText(string text, string filePath = "_ViewImports.cshtml") + { + var projectItem = CreateProjectItemFromText(text, filePath); + FileSystem.Add(projectItem); + return projectItem; + } + + private RazorProjectItem CreateProjectItemFromText(string text, string filePath) + { + // Consider the file path to be relative to the 'FileName' of the test. + var workingDirectory = Path.GetDirectoryName(FileName); + + // Since these paths are used in baselines, we normalize them to windows style. We + // use "" as the base path by convention to avoid baking in an actual file system + // path. + var basePath = ""; + var physicalPath = Path.Combine(workingDirectory, filePath).Replace('/', '\\'); + var relativePhysicalPath = physicalPath; + + // FilePaths in Razor are **always** are of the form '/a/b/c.cshtml' + filePath = physicalPath.Replace('\\', '/'); + if (!filePath.StartsWith("/")) + { + filePath = '/' + filePath; + } + + text = NormalizeNewLines(text); + + var projectItem = new TestRazorProjectItem( + basePath: basePath, + filePath: filePath, + physicalPath: physicalPath, + relativePhysicalPath: relativePhysicalPath) + { + Content = text, + }; + + return projectItem; + } + + protected RazorProjectItem CreateProjectItemFromFile(string filePath = null) { if (FileName == null) { - var message = $"{nameof(CreateProjectItem)} should only be called from an integration test, ({nameof(FileName)} is null)."; + var message = $"{nameof(CreateProjectItemFromFile)} should only be called from an integration test, ({nameof(FileName)} is null)."; throw new InvalidOperationException(message); } @@ -102,12 +199,149 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests var fileContent = testFile.ReadAllText(); var normalizedContent = NormalizeNewLines(fileContent); - var projectItem = new TestRazorProjectItem(sourceFileName) + var workingDirectory = Path.GetDirectoryName(FileName); + var fullPath = sourceFileName; + + // Normalize to forward-slash - these strings end up in the baselines. + fullPath = fullPath.Replace('\\', '/'); + sourceFileName = sourceFileName.Replace('\\', '/'); + + // FilePaths in Razor are **always** are of the form '/a/b/c.cshtml' + filePath = filePath ?? sourceFileName; + if (!filePath.StartsWith("/")) { - Content = normalizedContent, + filePath = '/' + filePath; + } + + var projectItem = new TestRazorProjectItem( + basePath: workingDirectory, + filePath: filePath, + physicalPath: fullPath, + relativePhysicalPath: sourceFileName) + { + Content = fileContent, + }; + + return projectItem; + } + + protected CompiledCSharpCode CompileToCSharp(string text, string path = "test.cshtml", bool? designTime = null) + { + var projectItem = CreateProjectItemFromText(text, path); + return CompileToCSharp(projectItem, designTime); + } + + protected CompiledCSharpCode CompileToCSharp(RazorProjectItem projectItem, bool? designTime = null) + { + var compilation = CreateCompilation(); + var references = compilation.References.Concat(new[] { compilation.ToMetadataReference(), }).ToArray(); + + var projectEngine = CreateProjectEngine(Configuration, references, ConfigureProjectEngine); + var codeDocument = (designTime ?? DesignTime) ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem); + + return new CompiledCSharpCode(CSharpCompilation.Create(compilation.AssemblyName + ".Views", references: references, options: CSharpCompilationOptions), codeDocument); + } + + protected CompiledAssembly CompileToAssembly(string text, string path = "test.cshtml", bool? designTime = null, bool throwOnFailure = true) + { + var compiled = CompileToCSharp(text, path, designTime); + return CompileToAssembly(compiled); + } + + protected CompiledAssembly CompileToAssembly(RazorProjectItem projectItem, bool? designTime = null, bool throwOnFailure = true) + { + var compiled = CompileToCSharp(projectItem, designTime); + return CompileToAssembly(compiled, throwOnFailure: throwOnFailure); + } + + protected CompiledAssembly CompileToAssembly(CompiledCSharpCode code, bool throwOnFailure = true) + { + var cSharpDocument = code.CodeDocument.GetCSharpDocument(); + if (cSharpDocument.Diagnostics.Any()) + { + var diagnosticsLog = string.Join(Environment.NewLine, cSharpDocument.Diagnostics.Select(d => d.ToString()).ToArray()); + throw new InvalidOperationException($"Aborting compilation to assembly because RazorCompiler returned nonempty diagnostics: {diagnosticsLog}"); + } + + var syntaxTrees = new[] + { + (CSharpSyntaxTree)CSharpSyntaxTree.ParseText(cSharpDocument.GeneratedCode, CSharpParseOptions, path: code.CodeDocument.Source.FilePath), }; - return projectItem; + var compilation = code.BaseCompilation.AddSyntaxTrees(syntaxTrees); + + var diagnostics = compilation + .GetDiagnostics() + .Where(d => d.Severity >= DiagnosticSeverity.Warning) + .ToArray(); + + if (diagnostics.Length > 0 && throwOnFailure) + { + throw new CompilationFailedException(compilation, diagnostics); + } + else if (diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error)) + { + return new CompiledAssembly(compilation, code.CodeDocument, assembly: null); + } + + using (var peStream = new MemoryStream()) + { + var emit = compilation.Emit(peStream); + diagnostics = emit + .Diagnostics + .Where(d => d.Severity >= DiagnosticSeverity.Warning) + .ToArray(); + if (diagnostics.Length > 0 && throwOnFailure) + { + throw new CompilationFailedException(compilation, diagnostics); + } + + return new CompiledAssembly(compilation, code.CodeDocument, Assembly.Load(peStream.ToArray())); + } + } + + private CSharpCompilation CreateCompilation() + { + var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTrees); + var diagnostics = compilation.GetDiagnostics().Where(d => d.Severity >= DiagnosticSeverity.Warning).ToArray(); + if (diagnostics.Length > 0) + { + throw new CompilationFailedException(compilation, diagnostics); + } + + return compilation; + } + + protected RazorProjectEngine CreateProjectEngine(Action configure = null) + { + var compilation = CreateCompilation(); + var references = compilation.References.Concat(new[] { compilation.ToMetadataReference(), }).ToArray(); + return CreateProjectEngine(Configuration, references, configure); + } + + private RazorProjectEngine CreateProjectEngine(RazorConfiguration configuration, MetadataReference[] references, Action configure) + { + return RazorProjectEngine.Create(configuration, FileSystem, b => + { + b.Phases.Insert(0, new ConfigureCodeRenderingPhase(LineEnding)); + + configure?.Invoke(b); + + // Allow the test to do custom things with tag helpers, but do the default thing most of the time. + if (!b.Features.OfType().Any()) + { + b.Features.Add(new CompilationTagHelperFeature()); + b.Features.Add(new DefaultMetadataReferenceFeature() + { + References = references, + }); + } + + // Decorate the import feature so we can normalize line endings. + var importFeature = b.Features.OfType().FirstOrDefault(); + b.Features.Add(new NormalizedDefaultImportFeature(importFeature, LineEnding)); + b.Features.Remove(importFeature); + }); } protected void AssertDocumentNodeMatchesBaseline(DocumentIntermediateNode document) @@ -225,96 +459,93 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests Assert.Equal(baseline, actual); } - private static string NormalizeNewLines(string content) + private string NormalizeNewLines(string content) { - return Regex.Replace(content, "(? GetImports(RazorProjectItem projectItem) { - var imports = new List(); - - while (true) + if (_inner == null) { - var importsFileName = Path.ChangeExtension(projectItem.FilePathWithoutExtension + "_Imports" + imports.Count.ToString(), ".cshtml"); - var importsFile = TestFile.Create(importsFileName, _assembly); - if (!importsFile.Exists()) - { - break; - } - - var importContent = importsFile.ReadAllText(); - var normalizedContent = NormalizeNewLines(importContent); - var importItem = new TestRazorProjectItem(importsFileName) - { - Content = normalizedContent - }; - imports.Add(importItem); + return Array.Empty(); } - imports.AddRange(_existingImportFeature.GetImports(projectItem)); + var normalizedImports = new List(); + var imports = _inner.GetImports(projectItem); + foreach (var import in imports) + { + if (import.Exists) + { + var text = string.Empty; + using (var stream = import.Read()) + using (var reader = new StreamReader(stream)) + { + text = reader.ReadToEnd().Trim(); + } - return imports; - } - } + // It's important that we normalize the newlines in the default imports. The default imports will + // be created with Environment.NewLine, but we need to normalize to `\r\n` so that the indices + // are the same on xplat. + var normalizedText = NormalizeNewLines(text, _lineEnding); + var normalizedImport = new TestRazorProjectItem(import.FilePath, import.PhysicalPath, import.RelativePhysicalPath, import.BasePath) + { + Content = normalizedText + }; - private class IntegrationTestFileSystem : RazorProjectFileSystem - { - public static IntegrationTestFileSystem Default = new IntegrationTestFileSystem(); + normalizedImports.Add(normalizedImport); + } + } - private IntegrationTestFileSystem() - { - } - - public override IEnumerable EnumerateItems(string basePath) - { - return Enumerable.Empty(); - } - - public override RazorProjectItem GetItem(string path) - { - return new NotFoundProjectItem(string.Empty, path); - } - - public override IEnumerable FindHierarchicalItems(string basePath, string path, string fileName) - { - return Enumerable.Empty(); + return normalizedImports; } } } diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Microsoft.AspNetCore.Razor.Test.Common.csproj b/test/Microsoft.AspNetCore.Razor.Test.Common/Microsoft.AspNetCore.Razor.Test.Common.csproj index d1f72c96bb..95f1644d40 100644 --- a/test/Microsoft.AspNetCore.Razor.Test.Common/Microsoft.AspNetCore.Razor.Test.Common.csproj +++ b/test/Microsoft.AspNetCore.Razor.Test.Common/Microsoft.AspNetCore.Razor.Test.Common.csproj @@ -9,6 +9,7 @@ +