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.
This commit is contained in:
Ryan Nowak 2018-09-26 22:51:51 -07:00
parent f463325e9a
commit f425134ffe
26 changed files with 1134 additions and 772 deletions

View File

@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.IntegrationTests
b.AddTargetExtension(new TemplateTargetExtension());
});
var projectItem = CreateProjectItem();
var projectItem = CreateProjectItemFromFile();
// Act
var document = engine.Process(projectItem);

View File

@ -10,7 +10,6 @@ Document -
UsingDirective - (178:6,1 [43] ) - Microsoft.AspNetCore.Mvc.ViewFeatures
ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InheritsWithViewImports - MyPageModel<MyModel> -
DesignTimeDirective -
DirectiveToken - (10:0,10 [19] InheritsWithViewImports_Imports0.cshtml) - MyPageModel<TModel>
DirectiveToken - (231:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel>
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<TModel>
DirectiveToken - (14:1,7 [7] InheritsWithViewImports.cshtml) - MyModel
CSharpCode -
IntermediateToken - - CSharp - #pragma warning disable 0414

View File

@ -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<MyModel>
{
#pragma warning disable 1998

View File

@ -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<TModel>
{
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<TModel> : 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<TModel> : RazorPage
public class MyModel
{
}
";
}");
var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(appCode));
RunDesignTimeTest(compilation);
AddProjectItemFromText(@"@inherits MyBasePageForViews<TModel>");
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<TModel>
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<TModel>
{
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<TModel>
{
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<string> 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<string> 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<IImportProjectFeature>().Single();
b.SetImportFeature(new NormalizedDefaultImportFeature(existingImportFeature));
b.Features.Add(GetMetadataReferenceFeature(references));
b.Features.Add(new CompilationTagHelperFeature());
});
}
private static IRazorEngineFeature GetMetadataReferenceFeature(IEnumerable<MetadataReference> 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<RazorProjectItem> GetImports(RazorProjectItem projectItem)
{
var normalizedImports = new List<RazorProjectItem>();
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, "(?<!\r)\n", "\r\n", RegexOptions.None, TimeSpan.FromSeconds(10));
var normalizedImport = new TestRazorProjectItem(import.FilePath, import.PhysicalPath, import.RelativePhysicalPath, import.BasePath)
{
Content = normalizedText
};
normalizedImports.Add(normalizedImport);
}
return normalizedImports;
}
AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode());
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument());
AssertSourceMappingsMatchBaseline(compiled.CodeDocument);
}
}
}

View File

@ -10,7 +10,6 @@ Document -
UsingDirective - (178:6,1 [43] ) - Microsoft.AspNetCore.Mvc.ViewFeatures
ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InheritsWithViewImports - MyBasePageForViews<MyModel> -
DesignTimeDirective -
DirectiveToken - (10:0,10 [26] InheritsWithViewImports_Imports0.cshtml) - MyBasePageForViews<TModel>
DirectiveToken - (231:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel>
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<TModel>
DirectiveToken - (7:0,7 [7] InheritsWithViewImports.cshtml) - MyModel
CSharpCode -
IntermediateToken - - CSharp - #pragma warning disable 0414

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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);

View File

@ -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());
}
}
}

View File

@ -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);

View File

@ -1,37 +0,0 @@
// <auto-generated/>
#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

View File

@ -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 - <p>
IntermediateToken - (3:0,3 [9] BasicImports.cshtml) - Html - Hi there!
IntermediateToken - (12:0,12 [4] BasicImports.cshtml) - Html - </p>
IntermediateToken - (16:0,16 [2] BasicImports.cshtml) - Html - \n

View File

@ -1,5 +0,0 @@
@using System.Globalization
@using System.ComponentModel
@inherits Hello
@("And also this")
<p>This will get ignored</p>

View File

@ -1,36 +0,0 @@
#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicImports.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "d5e5d28c0c504a7b0c5207a5230a5b7327ce5e09"
// <auto-generated/>
#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("<p>Hi there!</p>\r\n");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591

View File

@ -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 - <p>
IntermediateToken - (3:0,3 [9] BasicImports.cshtml) - Html - Hi there!
IntermediateToken - (12:0,12 [4] BasicImports.cshtml) - Html - </p>
IntermediateToken - (16:0,16 [2] BasicImports.cshtml) - Html - \n

View File

@ -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<SyntaxTree>();
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();
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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<string> _fileName = new AsyncLocal<string>();
#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<SyntaxTree>(),
referenceAssemblies,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
}
protected IntegrationTestBase(bool? generateBaselines = null)
{
TestProjectRoot = TestProject.GetProjectDirectory(GetType());
if (generateBaselines.HasValue)
{
GenerateBaselines = generateBaselines.Value;
}
}
/// <summary>
/// Gets the <see cref="CSharpCompilation"/> that will be used as the 'app' compilation.
/// </summary>
protected virtual CSharpCompilation BaseCompilation => DefaultBaseCompilation;
/// <summary>
/// Gets the parse options applied when using <see cref="AddCSharpSyntaxTree(string, string)"/>.
/// </summary>
protected virtual CSharpParseOptions CSharpParseOptions { get; } = new CSharpParseOptions(LanguageVersion.Latest);
/// <summary>
/// Gets the compilation options applied when compiling assemblies.
/// </summary>
protected virtual CSharpCompilationOptions CSharpCompilationOptions { get; } = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
/// <summary>
/// Gets a list of CSharp syntax trees used that are considered part of the 'app'.
/// </summary>
protected virtual List<CSharpSyntaxTree> CSharpSyntaxTrees { get; } = new List<CSharpSyntaxTree>();
/// <summary>
/// Gets the <see cref="RazorConfiguration"/> that will be used for code generation.
/// </summary>
protected virtual RazorConfiguration Configuration { get; } = RazorConfiguration.Default;
protected virtual bool DesignTime { get; } = false;
/// <summary>
/// Gets the
/// </summary>
internal VirtualRazorProjectFileSystem FileSystem { get; } = new VirtualRazorProjectFileSystem();
/// <summary>
/// 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.
/// </summary>
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<RazorProjectEngineBuilder> 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<IImportProjectFeature>().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<RazorProjectEngineBuilder> 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<RazorProjectEngineBuilder> 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<ITagHelperFeature>().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<IImportProjectFeature>().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, "(?<!\r)\n", "\r\n", RegexOptions.None, TimeSpan.FromSeconds(10));
return NormalizeNewLines(content, LineEnding);
}
private class IntegrationTestProjectEngine : DefaultRazorProjectEngine
private static string NormalizeNewLines(string content, string lineEnding)
{
public IntegrationTestProjectEngine(
RazorProjectEngine innerEngine)
: base(innerEngine.Configuration, innerEngine.Engine, innerEngine.FileSystem, innerEngine.ProjectFeatures)
return Regex.Replace(content, "(?<!\r)\n", lineEnding, RegexOptions.None, TimeSpan.FromSeconds(10));
}
// This is to prevent you from accidentally checking in with GenerateBaselines = true
[Fact]
public void GenerateBaselinesMustBeFalse()
{
Assert.False(GenerateBaselines, "GenerateBaselines should be set back to false before you check in!");
}
private class ConfigureCodeRenderingPhase : RazorEnginePhaseBase
{
public ConfigureCodeRenderingPhase(string lineEnding)
{
LineEnding = lineEnding;
}
protected override void ProcessCore(RazorCodeDocument codeDocument)
public string LineEnding { get; }
protected override void ExecuteCore(RazorCodeDocument codeDocument)
{
// This will ensure that we're not putting any randomly generated data in a baseline.
codeDocument.Items[CodeRenderingContext.SuppressUniqueIds] = "test";
// This is to make tests work cross platform.
codeDocument.Items[CodeRenderingContext.NewLineString] = "\r\n";
base.ProcessCore(codeDocument);
codeDocument.Items[CodeRenderingContext.NewLineString] = LineEnding;
}
}
private class IntegrationTestImportFeature : RazorProjectEngineFeatureBase, IImportProjectFeature
// 'Default' imports won't have normalized line-endings, which is unfriendly for testing.
private class NormalizedDefaultImportFeature : RazorProjectEngineFeatureBase, IImportProjectFeature
{
private Assembly _assembly;
private IImportProjectFeature _existingImportFeature;
private readonly IImportProjectFeature _inner;
private readonly string _lineEnding;
public IntegrationTestImportFeature(Assembly assembly, IImportProjectFeature existingImportFeature)
public NormalizedDefaultImportFeature(IImportProjectFeature inner, string lineEnding)
{
_assembly = assembly;
_existingImportFeature = existingImportFeature;
_inner = inner;
_lineEnding = lineEnding;
}
protected override void OnInitialized()
{
_existingImportFeature.ProjectEngine = ProjectEngine;
if (_inner != null)
{
_inner.ProjectEngine = ProjectEngine;
}
}
public IReadOnlyList<RazorProjectItem> GetImports(RazorProjectItem projectItem)
{
var imports = new List<RazorProjectItem>();
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<RazorProjectItem>();
}
imports.AddRange(_existingImportFeature.GetImports(projectItem));
var normalizedImports = new List<RazorProjectItem>();
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<RazorProjectItem> EnumerateItems(string basePath)
{
return Enumerable.Empty<RazorProjectItem>();
}
public override RazorProjectItem GetItem(string path)
{
return new NotFoundProjectItem(string.Empty, path);
}
public override IEnumerable<RazorProjectItem> FindHierarchicalItems(string basePath, string path, string fileName)
{
return Enumerable.Empty<RazorProjectItem>();
return normalizedImports;
}
}
}

View File

@ -9,6 +9,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Language\Microsoft.AspNetCore.Razor.Language.csproj" />
<ProjectReference Include="..\..\src\Microsoft.CodeAnalysis.Razor\Microsoft.CodeAnalysis.Razor.csproj" />
</ItemGroup>
<ItemGroup>