Make the tests pass

- Remove baseline tests. Those live with tooling
- Unskip tests that should have been fixed years ago
- Fix rendering test infrastructure
This commit is contained in:
Ryan Nowak 2018-12-29 16:06:13 -08:00
parent bdb5982dbd
commit 0dd1bf8cd3
27 changed files with 162 additions and 4319 deletions

View File

@ -2,14 +2,19 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.Build.Test
{
public class BindRazorIntegrationTest : RazorIntegrationTestBase
{
public BindRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
internal override bool UseTwoPhaseCompilation => true;
[Fact]

View File

@ -1,13 +1,11 @@
// 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.Linq;
using Microsoft.AspNetCore.Components.Razor;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.Build.Test
{
@ -84,6 +82,11 @@ namespace Test
}
");
public ChildContentRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
internal override bool UseTwoPhaseCompilation => true;
[Fact]
@ -410,174 +413,5 @@ namespace Test
frame => AssertFrame.Text(frame, " Content", 9),
frame => AssertFrame.Text(frame, "Bye!", 11));
}
[Fact]
public void Render_ChildContent_AttributeAndBody_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
// Act
var generated = CompileToCSharp(@"
@addTagHelper *, TestAssembly
@{ RenderFragment<string> template = @<div>@context.ToLowerInvariant()</div>; }
<RenderChildContent ChildContent=""@template.WithValue(""HI"")"">
Some Content
</RenderChildContent>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.ChildContentSetByAttributeAndBody.Id, diagnostic.Id);
}
[Fact]
public void Render_ChildContent_AttributeAndExplicitChildContent_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
// Act
var generated = CompileToCSharp(@"
@addTagHelper *, TestAssembly
@{ RenderFragment<string> template = @<div>@context.ToLowerInvariant()</div>; }
<RenderChildContent ChildContent=""@template.WithValue(""HI"")"">
<ChildContent>
Some Content
</ChildContent>
</RenderChildContent>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.ChildContentSetByAttributeAndBody.Id, diagnostic.Id);
}
[Fact]
public void Render_ChildContent_ExplicitChildContent_UnrecogizedContent_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
// Act
var generated = CompileToCSharp(@"
@addTagHelper *, TestAssembly
<RenderChildContent>
<ChildContent>
</ChildContent>
@somethingElse
</RenderChildContent>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.ChildContentMixedWithExplicitChildContent.Id, diagnostic.Id);
Assert.Equal(
"Unrecognized child content inside component 'RenderChildContent'. The component 'RenderChildContent' accepts " +
"child content through the following top-level items: 'ChildContent'.",
diagnostic.GetMessage());
}
[Fact]
public void Render_ChildContent_ExplicitChildContent_UnrecogizedElement_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
// Act
var generated = CompileToCSharp(@"
@addTagHelper *, TestAssembly
<RenderChildContent>
<ChildContent>
</ChildContent>
<UnrecognizedChildContent></UnrecognizedChildContent>
</RenderChildContent>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.ChildContentMixedWithExplicitChildContent.Id, diagnostic.Id);
}
[Fact]
public void Render_ChildContent_ExplicitChildContent_UnrecogizedAttribute_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
// Act
var generated = CompileToCSharp(@"
@addTagHelper *, TestAssembly
<RenderChildContent>
<ChildContent attr>
</ChildContent>
</RenderChildContent>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.ChildContentHasInvalidAttribute.Id, diagnostic.Id);
}
[Fact]
public void Render_ChildContent_ExplicitChildContent_InvalidParameterName_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
// Act
var generated = CompileToCSharp(@"
@addTagHelper *, TestAssembly
<RenderChildContentString>
<ChildContent Context=""@(""HI"")"">
</ChildContent>
</RenderChildContentString>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.ChildContentHasInvalidParameter.Id, diagnostic.Id);
}
[Fact]
public void Render_ChildContent_ExplicitChildContent_RepeatedParameterName_GeneratesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
// Act
var generated = CompileToCSharp(@"
@addTagHelper *, TestAssembly
<RenderChildContentString>
<ChildContent>
<RenderChildContentString>
<ChildContent Context=""context"">
</ChildContent>
</RenderChildContentString>
</ChildContent>
</RenderChildContentString>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.ChildContentRepeatedParameterName.Id, diagnostic.Id);
Assert.Equal(
"The child content element 'ChildContent' of component 'RenderChildContentString' uses the same parameter name ('context') as enclosing child content " +
"element 'ChildContent' of component 'RenderChildContentString'. Specify the parameter name like: '<ChildContent Context=\"another_name\"> to resolve the ambiguity",
diagnostic.GetMessage());
}
[Fact]
public void Render_ChildContent_ContextParameterNameOnComponent_Invalid_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
// Act
var generated = CompileToCSharp(@"
@addTagHelper *, TestAssembly
<RenderChildContentString Context=""@Foo()"">
</RenderChildContentString>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.ChildContentHasInvalidParameterOnComponent.Id, diagnostic.Id);
Assert.Equal(
"Invalid parameter name. The parameter name attribute 'Context' on component 'RenderChildContentString' can only include literal text.",
diagnostic.GetMessage());
}
}
}

View File

@ -1,127 +0,0 @@
// 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.IO;
using System.Text;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
namespace Microsoft.AspNetCore.Components.Build.Test
{
public class ComponentDiscoveryRazorIntegrationTest : RazorIntegrationTestBase
{
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void ComponentDiscovery_CanFindComponent_DefinedinCSharp()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
}
}
"));
// Act
var result = CompileToCSharp("@addTagHelper *, TestAssembly");
// Assert
var bindings = result.CodeDocument.GetTagHelperContext();
Assert.Single(bindings.TagHelpers, t => t.Name == "Test.MyComponent");
}
[Fact]
public void ComponentDiscovery_CanFindComponent_WithNamespace_DefinedinCSharp()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test.AnotherNamespace
{
public class MyComponent : ComponentBase
{
}
}
"));
// Act
var result = CompileToCSharp("@addTagHelper *, TestAssembly");
// Assert
var bindings = result.CodeDocument.GetTagHelperContext();
Assert.Single(bindings.TagHelpers, t => t.Name == "Test.AnotherNamespace.MyComponent");
}
[Fact]
public void ComponentDiscovery_CanFindComponent_DefinedinCshtml()
{
// Arrange
// Act
var result = CompileToCSharp("UniqueName.cshtml", "@addTagHelper *, TestAssembly");
// Assert
var bindings = result.CodeDocument.GetTagHelperContext();
Assert.Single(bindings.TagHelpers, t => t.Name == "Test.UniqueName");
}
[Fact]
public void ComponentDiscovery_CanFindComponent_BuiltIn()
{
// Arrange
// Act
var result = CompileToCSharp("@addTagHelper *, Microsoft.AspNetCore.Components");
// Assert
var bindings = result.CodeDocument.GetTagHelperContext();
Assert.Single(bindings.TagHelpers, t => t.Name == "Microsoft.AspNetCore.Components.Routing.NavLink");
}
[Fact]
public void ComponentDiscovery_CanFindComponent_WithTypeParameter()
{
// Arrange
// Act
var result = CompileToCSharp("UniqueName.cshtml", @"
@addTagHelper *, TestAssembly
@typeparam TItem
@functions {
[Parameter] TItem Item { get; set; }
}");
// Assert
var bindings = result.CodeDocument.GetTagHelperContext();
Assert.Single(bindings.TagHelpers, t => t.Name == "Test.UniqueName<TItem>");
}
[Fact]
public void ComponentDiscovery_CanFindComponent_WithMultipleTypeParameters()
{
// Arrange
// Act
var result = CompileToCSharp("UniqueName.cshtml", @"
@addTagHelper *, TestAssembly
@typeparam TItem1
@typeparam TItem2
@typeparam TItem3
@functions {
[Parameter] TItem1 Item { get; set; }
}");
// Assert
var bindings = result.CodeDocument.GetTagHelperContext();
Assert.Single(bindings.TagHelpers, t => t.Name == "Test.UniqueName<TItem1, TItem2, TItem3>");
}
}
}

View File

@ -3,15 +3,20 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.Build.Test
{
public class ComponentRenderingRazorIntegrationTest : RazorIntegrationTestBase
{
public ComponentRenderingRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
internal override bool UseTwoPhaseCompilation => true;
[Fact]
@ -469,12 +474,11 @@ namespace Test
frame => AssertFrame.Attribute(frame, "style", "background: #FFFFFF;", 2));
}
// Text nodes decode HTML entities
[Fact]
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/6185")]
public void Render_Component_HtmlEncoded()
{
// Arrange
var component = CompileToComponent(@"&lt;span&gt;Hi&lt/span&gt;");
var component = CompileToComponent(@"&lt;span&gt;Hi&lt;/span&gt;");
// Act
var frames = GetRenderTree(component);
@ -485,8 +489,23 @@ namespace Test
frame => AssertFrame.Text(frame, "<span>Hi</span>"));
}
// Integration test for HTML block rewriting
[Fact]
public void Render_Component_HtmlBlockEncoded()
{
// Arrange
var component = CompileToComponent(@"<div>&lt;span&gt;Hi&lt/span&gt;</div>");
// Act
var frames = GetRenderTree(component);
// Assert
Assert.Collection(
frames,
frame => AssertFrame.Markup(frame, "<div>&lt;span&gt;Hi&lt/span&gt;</div>"));
}
// Integration test for HTML block rewriting
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/6183")]
public void Render_HtmlBlock_Integration()
{
// Arrange

View File

@ -1,168 +0,0 @@
// 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 System.Text;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Razor;
using Microsoft.AspNetCore.Razor.Language;
using Xunit;
namespace Microsoft.AspNetCore.Components.Build.Test
{
public class DeclarationRazorIntegrationTest : RazorIntegrationTestBase
{
internal override RazorConfiguration Configuration => BlazorExtensionInitializer.DeclarationConfiguration;
[Fact]
public void DeclarationConfiguration_IncludesFunctions()
{
// Arrange & Act
var component = CompileToComponent(@"
@functions {
public string Value { get; set; }
}");
// Assert
var property = component.GetType().GetProperty("Value");
Assert.NotNull(property);
Assert.Same(typeof(string), property.PropertyType);
}
[Fact]
public void DeclarationConfiguration_IncludesInject()
{
// Arrange & Act
var component = CompileToComponent(@"
@inject string Value
");
// Assert
var property = component.GetType().GetProperty("Value", BindingFlags.NonPublic | BindingFlags.Instance);
Assert.NotNull(property);
Assert.Same(typeof(string), property.PropertyType);
}
[Fact]
public void DeclarationConfiguration_IncludesUsings()
{
// Arrange & Act
var component = CompileToComponent(@"
@using System.Text
@inject StringBuilder Value
");
// Assert
var property = component.GetType().GetProperty("Value", BindingFlags.NonPublic | BindingFlags.Instance);
Assert.NotNull(property);
Assert.Same(typeof(StringBuilder), property.PropertyType);
}
[Fact]
public void DeclarationConfiguration_IncludesInherits()
{
// Arrange & Act
var component = CompileToComponent($@"
@inherits {FullTypeName<BaseClass>()}
");
// Assert
Assert.Same(typeof(BaseClass), component.GetType().BaseType);
}
[Fact]
public void DeclarationConfiguration_IncludesImplements()
{
// Arrange & Act
var component = CompileToComponent($@"
@implements {FullTypeName<IDoCoolThings>()}
");
// Assert
var type = component.GetType();
Assert.Contains(typeof(IDoCoolThings), component.GetType().GetInterfaces());
}
[Fact]
public void DeclarationConfiguration_RenderMethodIsEmpty()
{
// Arrange & Act
var component = CompileToComponent(@"
<html>
@{ var message = ""hi""; }
<span class=""@(5 + 7)"">@message</span>
</html>
");
var frames = GetRenderTree(component);
// Assert
Assert.Empty(frames);
}
[Fact] // Regression test for https://github.com/aspnet/Blazor/issues/453
public void DeclarationConfiguration_FunctionsBlockHasLineMappings_MappingsApplyToError()
{
// Arrange & Act 1
var generated = CompileToCSharp(@"
@functions {
public StringBuilder Builder { get; set; }
}
");
// Assert 1
AssertSourceEquals(@"
// <auto-generated/>
#pragma warning disable 1591
#pragma warning disable 0414
#pragma warning disable 0649
#pragma warning disable 0169
namespace Test
{
#line hidden
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public class TestComponent : Microsoft.AspNetCore.Components.ComponentBase
{
#pragma warning disable 1998
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder)
{
}
#pragma warning restore 1998
#line 1 ""x:\dir\subdir\Test\TestComponent.cshtml""
public StringBuilder Builder { get; set; }
#line default
#line hidden
}
}
#pragma warning restore 1591
", generated);
// Act 2
var assembly = CompileToAssembly(generated, throwOnFailure: false);
// Assert 2
var diagnostic = Assert.Single(assembly.Diagnostics);
// This error should map to line 2 of the generated file, the test
// says 1 because Roslyn's line/column data structures are 0-based.
var position = diagnostic.Location.GetMappedLineSpan();
Assert.EndsWith(".cshtml", position.Path);
Assert.Equal(1, position.StartLinePosition.Line);
}
public class BaseClass : ComponentBase
{
}
public interface IDoCoolThings
{
}
}
}

View File

@ -1,10 +0,0 @@
// 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.
namespace Microsoft.AspNetCore.Components.Build.Test
{
public class DesignTimeCodeGenerationTest : CodeGenerationTestBase
{
internal override bool DesignTime => true;
}
}

View File

@ -1,65 +0,0 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Components.Build.Test
{
public class DiagnosticRazorIntegrationTest : RazorIntegrationTestBase
{
[Fact]
public void RejectsEndTagWithNoStartTag()
{
// Arrange/Act
var result = CompileToCSharp(
"Line1\nLine2\nLine3</mytag>");
// Assert
Assert.Collection(result.Diagnostics,
item =>
{
Assert.Equal("BL9981", item.Id);
Assert.Equal("Unexpected closing tag 'mytag' with no matching start tag.", item.GetMessage());
});
}
// This used to be a sugar syntax for lambdas, but we don't support that anymore
[Fact]
public void OldCodeBlockAttributeSyntax_ReportsError()
{
// Arrange/Act
var generated = CompileToCSharp(@"
<elem attr=@{ DidInvokeCode = true; } />
@functions {
public bool DidInvokeCode { get; set; } = false;
}");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("BL9979", diagnostic.Id);
}
[Fact]
public void RejectsScriptTag()
{
// Arrange/Act
var result = CompileToCSharp(@"Hello
<div>
<script src='anything'>
something
</script>
</div>
Goodbye");
// Assert
Assert.Collection(result.Diagnostics,
item =>
{
Assert.Equal("BL9992", item.Id);
Assert.Equal("Script tags should not be placed inside components because they cannot be updated dynamically. To fix this, move the script tag to the 'index.html' file or another static location. For more information see https://go.microsoft.com/fwlink/?linkid=872131", item.GetMessage());
Assert.Equal(2, item.Span.LineIndex);
Assert.Equal(4, item.Span.CharacterIndex);
});
}
}
}

View File

@ -4,16 +4,21 @@
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Layouts;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.Build.Test
{
// Integration tests for Blazor's directives
public class DirectiveRazorIntegrationTest : RazorIntegrationTestBase
{
public DirectiveRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
[Fact]
public void ComponentsDoNotHaveLayoutAttributeByDefault()
{

View File

@ -1,48 +0,0 @@
// 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.IO;
using Xunit;
namespace Microsoft.AspNetCore.Components.Build.Test
{
// Integration tests focused on file path handling for class/namespace names
public class FilePathRazorIntegrationTest : RazorIntegrationTestBase
{
[Fact]
public void FileNameIsInvalidClassName_SanitizesInvalidClassName()
{
// Arrange
// Act
var result = CompileToAssembly("Filename with spaces.cshtml", "");
// Assert
Assert.Empty(result.Diagnostics);
var type = Assert.Single(result.Assembly.GetTypes());
Assert.Equal(DefaultBaseNamespace, type.Namespace);
Assert.Equal("Filename_with_spaces", type.Name);
}
[Theory]
[InlineData("ItemAtRoot.cs", "Test", "ItemAtRoot")]
[InlineData("Dir1\\MyFile.cs", "Test.Dir1", "MyFile")]
[InlineData("Dir1\\Dir2\\MyFile.cs", "Test.Dir1.Dir2", "MyFile")]
public void CreatesClassWithCorrectNameAndNamespace(string relativePath, string expectedNamespace, string expectedClassName)
{
// Arrange
relativePath = relativePath.Replace('\\', Path.DirectorySeparatorChar);
// Act
var result = CompileToAssembly(relativePath, "");
// Assert
Assert.Empty(result.Diagnostics);
var type = Assert.Single(result.Assembly.GetTypes());
Assert.Equal(expectedNamespace, type.Namespace);
Assert.Equal(expectedClassName, type.Name);
}
}
}

View File

@ -1,15 +1,14 @@
// 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.AspNetCore.Components.Razor;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.Build.Test
{
@ -81,6 +80,11 @@ namespace Test
}
");
public GenericComponentRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
internal override bool UseTwoPhaseCompilation => true;
[Fact]
@ -313,45 +317,5 @@ namespace Test
frame => AssertFrame.Text(frame, "FOO", 1),
frame => AssertFrame.Text(frame, "39", 2));
}
[Fact]
public void GenericComponent_WithoutAnyTypeParameters_TriggersDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
// Act
var generated = CompileToCSharp(@"
@addTagHelper *, TestAssembly
<GenericContext />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.GenericComponentTypeInferenceUnderspecified.Id, diagnostic.Id);
Assert.Equal(
"The type of component 'GenericContext' cannot be inferred based on the values provided. Consider " +
"specifying the type arguments directly using the following attributes: 'TItem'.",
diagnostic.GetMessage());
}
[Fact]
public void GenericComponent_WithMissingTypeParameters_TriggersDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(MultipleGenericParameterComponent);
// Act
var generated = CompileToCSharp(@"
@addTagHelper *, TestAssembly
<MultipleGenericParameter TItem1=int />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.GenericComponentMissingTypeArgument.Id, diagnostic.Id);
Assert.Equal(
"The component 'MultipleGenericParameter' is missing required type arguments. " +
"Specify the missing types using the attributes: 'TItem2', 'TItem3'.",
diagnostic.GetMessage());
}
}
}

View File

@ -1,29 +0,0 @@
// 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.AspNetCore.Components.Build.Test;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class InitializeTestFileAttribute : BeforeAfterTestAttribute
{
public override void Before(MethodInfo methodUnderTest)
{
if (typeof(RazorBaselineIntegrationTestBase).GetTypeInfo().IsAssignableFrom(methodUnderTest.ReflectedType.GetTypeInfo()))
{
var typeName = methodUnderTest.ReflectedType.Name;
RazorBaselineIntegrationTestBase.DirectoryPath = $"TestFiles/{typeName}/{methodUnderTest.Name}";
}
}
public override void After(MethodInfo methodUnderTest)
{
if (typeof(RazorBaselineIntegrationTestBase).GetTypeInfo().IsAssignableFrom(methodUnderTest.ReflectedType.GetTypeInfo()))
{
RazorBaselineIntegrationTestBase.DirectoryPath = null;
}
}
}
}

View File

@ -1,46 +0,0 @@
// 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.IO;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public static class IntermediateNodeSerializer
{
public static string Serialize(IntermediateNode node)
{
using (var writer = new StringWriter())
{
var walker = new Walker(writer);
walker.Visit(node);
return writer.ToString();
}
}
private class Walker : IntermediateNodeWalker
{
private readonly IntermediateNodeWriter _visitor;
private readonly TextWriter _writer;
public Walker(TextWriter writer)
{
_visitor = new IntermediateNodeWriter(writer);
_writer = writer;
}
public TextWriter Writer { get; }
public override void VisitDefault(IntermediateNode node)
{
_visitor.Visit(node);
_writer.WriteLine();
_visitor.Depth++;
base.VisitDefault(node);
_visitor.Depth--;
}
}
}
}

View File

@ -1,275 +0,0 @@
// 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;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public static class IntermediateNodeVerifier
{
public static void Verify(IntermediateNode node, string[] baseline)
{
var walker = new Walker(baseline);
walker.Visit(node);
walker.AssertReachedEndOfBaseline();
}
private class Walker : IntermediateNodeWalker
{
private readonly string[] _baseline;
private readonly IntermediateNodeWriter _visitor;
private readonly StringWriter _writer;
private int _index;
public Walker(string[] baseline)
{
_writer = new StringWriter();
_visitor = new IntermediateNodeWriter(_writer);
_baseline = baseline;
}
public TextWriter Writer { get; }
public override void VisitDefault(IntermediateNode node)
{
var expected = _index < _baseline.Length ? _baseline[_index++] : null;
// Write the node as text for comparison
_writer.GetStringBuilder().Clear();
_visitor.Visit(node);
var actual = _writer.GetStringBuilder().ToString();
AssertNodeEquals(node, Ancestors, expected, actual);
_visitor.Depth++;
base.VisitDefault(node);
_visitor.Depth--;
}
public void AssertReachedEndOfBaseline()
{
// Since we're walking the nodes of our generated code there's the chance that our baseline is longer.
Assert.True(_baseline.Length == _index, "Not all lines of the baseline were visited!");
}
private void AssertNodeEquals(IntermediateNode node, IEnumerable<IntermediateNode> ancestors, string expected, string actual)
{
if (string.Equals(expected, actual))
{
// YAY!!! everything is great.
return;
}
if (expected == null)
{
var message = "The node is missing from baseline.";
throw new IntermediateNodeBaselineException(node, Ancestors.ToArray(), expected, actual, message);
}
int charsVerified = 0;
AssertNestingEqual(node, ancestors, expected, actual, ref charsVerified);
AssertNameEqual(node, ancestors, expected, actual, ref charsVerified);
AssertDelimiter(node, expected, actual, true, ref charsVerified);
AssertLocationEqual(node, ancestors, expected, actual, ref charsVerified);
AssertDelimiter(node, expected, actual, false, ref charsVerified);
AssertContentEqual(node, ancestors, expected, actual, ref charsVerified);
throw new InvalidOperationException("We can't figure out HOW these two things are different. This is a bug.");
}
private void AssertNestingEqual(IntermediateNode node, IEnumerable<IntermediateNode> ancestors, string expected, string actual, ref int charsVerified)
{
var i = 0;
for (; i < expected.Length; i++)
{
if (expected[i] != ' ')
{
break;
}
}
var failed = false;
var j = 0;
for (; j < i; j++)
{
if (actual.Length <= j || actual[j] != ' ')
{
failed = true;
break;
}
}
if (actual.Length <= j + 1 || actual[j] == ' ')
{
failed = true;
}
if (failed)
{
var message = "The node is at the wrong level of nesting. This usually means a child is missing.";
throw new IntermediateNodeBaselineException(node, ancestors.ToArray(), expected, actual, message);
}
charsVerified = j;
}
private void AssertNameEqual(IntermediateNode node, IEnumerable<IntermediateNode> ancestors, string expected, string actual, ref int charsVerified)
{
var expectedName = GetName(expected, charsVerified);
var actualName = GetName(actual, charsVerified);
if (!string.Equals(expectedName, actualName))
{
var message = $"Node names are not equal.";
throw new IntermediateNodeBaselineException(node, ancestors.ToArray(), expected, actual, message);
}
charsVerified += expectedName.Length;
}
// Either both strings need to have a delimiter next or neither should.
private void AssertDelimiter(IntermediateNode node, string expected, string actual, bool required, ref int charsVerified)
{
if (charsVerified == expected.Length && required)
{
throw new InvalidOperationException($"Baseline text is not well-formed: '{expected}'.");
}
if (charsVerified == actual.Length && required)
{
throw new InvalidOperationException($"Baseline text is not well-formed: '{actual}'.");
}
if (charsVerified == expected.Length && charsVerified == actual.Length)
{
return;
}
var expectedDelimiter = expected.IndexOf(" - ", charsVerified);
if (expectedDelimiter != charsVerified && expectedDelimiter != -1)
{
throw new InvalidOperationException($"Baseline text is not well-formed: '{actual}'.");
}
var actualDelimiter = actual.IndexOf(" - ", charsVerified);
if (actualDelimiter != charsVerified && actualDelimiter != -1)
{
throw new InvalidOperationException($"Baseline text is not well-formed: '{actual}'.");
}
Assert.Equal(expectedDelimiter, actualDelimiter);
charsVerified += 3;
}
private void AssertLocationEqual(IntermediateNode node, IEnumerable<IntermediateNode> ancestors, string expected, string actual, ref int charsVerified)
{
var expectedLocation = GetLocation(expected, charsVerified);
var actualLocation = GetLocation(actual, charsVerified);
if (!string.Equals(expectedLocation, actualLocation))
{
var message = $"Locations are not equal.";
throw new IntermediateNodeBaselineException(node, ancestors.ToArray(), expected, actual, message);
}
charsVerified += expectedLocation.Length;
}
private void AssertContentEqual(IntermediateNode node, IEnumerable<IntermediateNode> ancestors, string expected, string actual, ref int charsVerified)
{
var expectedContent = GetContent(expected, charsVerified);
var actualContent = GetContent(actual, charsVerified);
if (!string.Equals(expectedContent, actualContent))
{
var message = $"Contents are not equal.";
throw new IntermediateNodeBaselineException(node, ancestors.ToArray(), expected, actual, message);
}
charsVerified += expectedContent.Length;
}
private string GetName(string text, int start)
{
var delimiter = text.IndexOf(" - ", start);
if (delimiter == -1)
{
throw new InvalidOperationException($"Baseline text is not well-formed: '{text}'.");
}
return text.Substring(start, delimiter - start);
}
private string GetLocation(string text, int start)
{
var delimiter = text.IndexOf(" - ", start);
return delimiter == -1 ? text.Substring(start) : text.Substring(start, delimiter - start);
}
private string GetContent(string text, int start)
{
return start == text.Length ? string.Empty : text.Substring(start);
}
private class IntermediateNodeBaselineException : XunitException
{
public IntermediateNodeBaselineException(IntermediateNode node, IntermediateNode[] ancestors, string expected, string actual, string userMessage)
: base(Format(node, ancestors, expected, actual, userMessage))
{
Node = node;
Expected = expected;
Actual = actual;
}
public IntermediateNode Node { get; }
public string Actual { get; }
public string Expected { get; }
private static string Format(IntermediateNode node, IntermediateNode[] ancestors, string expected, string actual, string userMessage)
{
var builder = new StringBuilder();
builder.AppendLine(userMessage);
builder.AppendLine();
if (expected != null)
{
builder.Append("Expected: ");
builder.AppendLine(expected);
}
if (actual != null)
{
builder.Append("Actual: ");
builder.AppendLine(actual);
}
if (ancestors != null)
{
builder.AppendLine();
builder.AppendLine("Path:");
foreach (var ancestor in ancestors)
{
builder.AppendLine(ancestor.ToString());
}
}
return builder.ToString();
}
}
}
}
}

View File

@ -1,316 +0,0 @@
// 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.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Components.Razor;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
// Serializes single IR nodes (shallow).
public class IntermediateNodeWriter :
IntermediateNodeVisitor,
IExtensionIntermediateNodeVisitor<HtmlElementIntermediateNode>,
IExtensionIntermediateNodeVisitor<HtmlBlockIntermediateNode>,
IExtensionIntermediateNodeVisitor<ComponentExtensionNode>,
IExtensionIntermediateNodeVisitor<ComponentAttributeExtensionNode>,
IExtensionIntermediateNodeVisitor<ComponentChildContentIntermediateNode>,
IExtensionIntermediateNodeVisitor<ComponentTypeArgumentExtensionNode>,
IExtensionIntermediateNodeVisitor<ComponentTypeInferenceMethodIntermediateNode>,
IExtensionIntermediateNodeVisitor<RouteAttributeExtensionNode>,
IExtensionIntermediateNodeVisitor<RefExtensionNode>
{
private readonly TextWriter _writer;
public IntermediateNodeWriter(TextWriter writer)
{
_writer = writer;
}
public int Depth { get; set; }
public override void VisitDefault(IntermediateNode node)
{
WriteBasicNode(node);
}
public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
{
WriteContentNode(node, string.Join(" ", node.Modifiers), node.ClassName, node.BaseType, string.Join(", ", node.Interfaces ?? new List<string>()));
}
public override void VisitCSharpExpressionAttributeValue(CSharpExpressionAttributeValueIntermediateNode node)
{
WriteContentNode(node, node.Prefix);
}
public override void VisitCSharpCodeAttributeValue(CSharpCodeAttributeValueIntermediateNode node)
{
WriteContentNode(node, node.Prefix);
}
public override void VisitToken(IntermediateToken node)
{
WriteContentNode(node, node.Kind.ToString(), node.Content);
}
public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node)
{
WriteContentNode(node, node.DirectiveName);
}
public override void VisitDirective(DirectiveIntermediateNode node)
{
WriteContentNode(node, node.DirectiveName);
}
public override void VisitDirectiveToken(DirectiveTokenIntermediateNode node)
{
WriteContentNode(node, node.Content);
}
public override void VisitFieldDeclaration(FieldDeclarationIntermediateNode node)
{
WriteContentNode(node, string.Join(" ", node.Modifiers), node.FieldType, node.FieldName);
}
public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
{
WriteContentNode(node, node.Prefix, node.Suffix);
}
public override void VisitHtmlAttributeValue(HtmlAttributeValueIntermediateNode node)
{
WriteContentNode(node, node.Prefix);
}
public override void VisitNamespaceDeclaration(NamespaceDeclarationIntermediateNode node)
{
WriteContentNode(node, node.Content);
}
public override void VisitMethodDeclaration(MethodDeclarationIntermediateNode node)
{
WriteContentNode(node, string.Join(" ", node.Modifiers), node.ReturnType, node.MethodName);
}
public override void VisitUsingDirective(UsingDirectiveIntermediateNode node)
{
WriteContentNode(node, node.Content);
}
public override void VisitTagHelper(TagHelperIntermediateNode node)
{
WriteContentNode(node, node.TagName, string.Format("{0}.{1}", nameof(TagMode), node.TagMode));
}
public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
{
WriteContentNode(node, node.AttributeName, node.BoundAttribute.DisplayName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure));
}
public override void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node)
{
WriteContentNode(node, node.AttributeName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure));
}
public override void VisitExtension(ExtensionIntermediateNode node)
{
// This will be called for nodes that are internal implementation details of Razor,
// like the design time directive nodes.
if (node.GetType().Assembly == typeof(RazorCodeDocument).Assembly)
{
WriteBasicNode(node);
}
else
{
throw new InvalidOperationException("Unknown node type: " + node.GetType());
}
}
protected void WriteBasicNode(IntermediateNode node)
{
WriteIndent();
WriteName(node);
WriteSeparator();
WriteSourceRange(node);
}
protected void WriteContentNode(IntermediateNode node, params string[] content)
{
WriteIndent();
WriteName(node);
WriteSeparator();
WriteSourceRange(node);
for (var i = 0; i < content.Length; i++)
{
WriteSeparator();
WriteContent(content[i]);
}
}
protected void WriteIndent()
{
for (var i = 0; i < Depth; i++)
{
for (var j = 0; j < 4; j++)
{
_writer.Write(' ');
}
}
}
protected void WriteSeparator()
{
_writer.Write(" - ");
}
protected void WriteNewLine()
{
_writer.WriteLine();
}
protected void WriteName(IntermediateNode node)
{
var typeName = node.GetType().Name;
if (typeName.EndsWith("IntermediateNode"))
{
_writer.Write(typeName.Substring(0, typeName.Length - "IntermediateNode".Length));
}
else
{
_writer.Write(typeName);
}
}
protected void WriteSourceRange(IntermediateNode node)
{
if (node.Source != null)
{
WriteSourceRange(node.Source.Value);
}
}
protected void WriteSourceRange(SourceSpan sourceRange)
{
_writer.Write("(");
_writer.Write(sourceRange.AbsoluteIndex);
_writer.Write(":");
_writer.Write(sourceRange.LineIndex);
_writer.Write(",");
_writer.Write(sourceRange.CharacterIndex);
_writer.Write(" [");
_writer.Write(sourceRange.Length);
_writer.Write("] ");
if (sourceRange.FilePath != null)
{
var fileName = sourceRange.FilePath.Substring(sourceRange.FilePath.LastIndexOf('/') + 1);
_writer.Write(fileName);
}
_writer.Write(")");
}
protected void WriteDiagnostics(IntermediateNode node)
{
if (node.HasDiagnostics)
{
_writer.Write("| ");
for (var i = 0; i < node.Diagnostics.Count; i++)
{
var diagnostic = node.Diagnostics[i];
_writer.Write("{");
WriteSourceRange(diagnostic.Span);
_writer.Write(": ");
_writer.Write(diagnostic.Severity);
_writer.Write(" ");
_writer.Write(diagnostic.Id);
_writer.Write(": ");
// Purposefully not writing out the entire message to ensure readable IR and because messages
// can span multiple lines. Not using string.GetHashCode because we can't have any collisions.
using (var md5 = MD5.Create())
{
var diagnosticMessage = diagnostic.GetMessage();
var messageBytes = Encoding.UTF8.GetBytes(diagnosticMessage);
var messageHash = md5.ComputeHash(messageBytes);
var stringHashBuilder = new StringBuilder();
for (var j = 0; j < messageHash.Length; j++)
{
stringHashBuilder.Append(messageHash[j].ToString("x2"));
}
var stringHash = stringHashBuilder.ToString();
_writer.Write(stringHash);
}
_writer.Write("} ");
}
}
}
protected void WriteContent(string content)
{
if (content == null)
{
return;
}
// We explicitly escape newlines in node content so that the IR can be compared line-by-line. The escaped
// newline cannot be platform specific so we need to drop the windows \r.
// Also, escape our separator so we can search for ` - `to find delimiters.
_writer.Write(content.Replace("\r", string.Empty).Replace("\n", "\\n").Replace(" - ", "\\-"));
}
void IExtensionIntermediateNodeVisitor<HtmlElementIntermediateNode>.VisitExtension(HtmlElementIntermediateNode node)
{
WriteContentNode(node, node.TagName);
}
void IExtensionIntermediateNodeVisitor<HtmlBlockIntermediateNode>.VisitExtension(HtmlBlockIntermediateNode node)
{
WriteContentNode(node, node.Content);
}
void IExtensionIntermediateNodeVisitor<ComponentExtensionNode>.VisitExtension(ComponentExtensionNode node)
{
WriteContentNode(node, node.TagName, node.TypeName);
}
void IExtensionIntermediateNodeVisitor<ComponentAttributeExtensionNode>.VisitExtension(ComponentAttributeExtensionNode node)
{
WriteContentNode(node, node.AttributeName, node.PropertyName);
}
void IExtensionIntermediateNodeVisitor<ComponentChildContentIntermediateNode>.VisitExtension(ComponentChildContentIntermediateNode node)
{
WriteContentNode(node, node.AttributeName);
}
void IExtensionIntermediateNodeVisitor<ComponentTypeArgumentExtensionNode>.VisitExtension(ComponentTypeArgumentExtensionNode node)
{
WriteContentNode(node, node.TypeParameterName);
}
void IExtensionIntermediateNodeVisitor<ComponentTypeInferenceMethodIntermediateNode>.VisitExtension(ComponentTypeInferenceMethodIntermediateNode node)
{
WriteContentNode(node, node.FullTypeName, node.MethodName);
}
void IExtensionIntermediateNodeVisitor<RouteAttributeExtensionNode>.VisitExtension(RouteAttributeExtensionNode node)
{
WriteContentNode(node, node.Template);
}
void IExtensionIntermediateNodeVisitor<RefExtensionNode>.VisitExtension(RefExtensionNode node)
{
WriteContentNode(node, node.IdentifierToken.Content, node.IsComponentCapture ? node.ComponentCaptureTypeName : "Element");
}
}
}

View File

@ -1,13 +0,0 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Language
{
public class RazorDiagnosticSerializer
{
public static string Serialize(RazorDiagnostic diagnostic)
{
return diagnostic.ToString();
}
}
}

View File

@ -1,47 +0,0 @@
// 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.Text;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public static class SourceMappingsSerializer
{
public static string Serialize(RazorCSharpDocument csharpDocument, RazorSourceDocument sourceDocument)
{
var builder = new StringBuilder();
var charBuffer = new char[sourceDocument.Length];
sourceDocument.CopyTo(0, charBuffer, 0, sourceDocument.Length);
var sourceContent = new string(charBuffer);
for (var i = 0; i < csharpDocument.SourceMappings.Count; i++)
{
var sourceMapping = csharpDocument.SourceMappings[i];
builder.Append("Source Location: ");
AppendMappingLocation(builder, sourceMapping.OriginalSpan, sourceContent);
builder.Append("Generated Location: ");
AppendMappingLocation(builder, sourceMapping.GeneratedSpan, csharpDocument.GeneratedCode);
builder.AppendLine();
}
return builder.ToString();
}
private static void AppendMappingLocation(StringBuilder builder, SourceSpan location, string content)
{
builder
.AppendLine(location.ToString())
.Append("|");
for (var i = 0; i < location.Length; i++)
{
builder.Append(content[location.AbsoluteIndex + i]);
}
builder.AppendLine("|");
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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.IO;
@ -9,19 +9,30 @@ namespace Microsoft.AspNetCore.Razor.Language
{
private readonly byte[] _content;
public VirtualProjectItem(string basePath, string filePath, string physicalPath, string relativePhysicalPath, byte[] content)
public VirtualProjectItem(
string basePath,
string filePath,
string physicalPath,
string relativePhysicalPath,
string fileKind,
byte[] content)
{
BasePath = basePath;
FilePath = filePath;
PhysicalPath = physicalPath;
RelativePhysicalPath = relativePhysicalPath;
_content = content;
// Base class will detect based on file-extension.
FileKind = fileKind ?? base.FileKind;
}
public override string BasePath { get; }
public override string RelativePhysicalPath { get; }
public override string FileKind { get; }
public override string FilePath { get; }
public override string PhysicalPath { get; }

View File

@ -1,211 +0,0 @@
// 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.IO;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.IntegrationTests;
using Xunit;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Components.Build.Test
{
[InitializeTestFile]
public abstract class RazorBaselineIntegrationTestBase : RazorIntegrationTestBase
{
private static readonly AsyncLocal<string> _directoryPath = new AsyncLocal<string>();
protected RazorBaselineIntegrationTestBase(bool? generateBaselines = null)
{
TestProjectRoot = TestProject.GetProjectDirectory(GetType());
if (generateBaselines.HasValue)
{
GenerateBaselines = generateBaselines.Value;
}
}
// Used by the test framework to set the directory for test files.
public static string DirectoryPath
{
get { return _directoryPath.Value; }
set { _directoryPath.Value = value; }
}
#if GENERATE_BASELINES
protected bool GenerateBaselines { get; } = true;
#else
protected bool GenerateBaselines { get; } = false;
#endif
protected string TestProjectRoot { get; }
// For consistent line endings because the character counts are going to be recorded in files.
internal override string LineEnding => "\r\n";
internal override bool NormalizeSourceLineEndings => true;
internal override string PathSeparator => "\\";
// Force consistent paths since they are going to be recorded in files.
internal override string WorkingDirectory => ArbitraryWindowsPath;
[Fact]
public void GenerateBaselinesMustBeFalse()
{
Assert.False(GenerateBaselines, "GenerateBaselines should be set back to false before you check in!");
}
protected void AssertDocumentNodeMatchesBaseline(RazorCodeDocument codeDocument)
{
var document = codeDocument.GetDocumentIntermediateNode();
var baselineFilePath = GetBaselineFilePath(codeDocument, ".ir.txt");
if (GenerateBaselines)
{
var baselineFullPath = Path.Combine(TestProjectRoot, baselineFilePath);
Directory.CreateDirectory(Path.GetDirectoryName(baselineFullPath));
WriteBaseline(IntermediateNodeSerializer.Serialize(document), baselineFullPath);
return;
}
var irFile = TestFile.Create(baselineFilePath, GetType().Assembly);
if (!irFile.Exists())
{
throw new XunitException($"The resource {baselineFilePath} was not found.");
}
// Normalize newlines by splitting into an array.
var baseline = irFile.ReadAllText().Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
IntermediateNodeVerifier.Verify(document, baseline);
}
protected void AssertCSharpDocumentMatchesBaseline(RazorCodeDocument codeDocument)
{
var document = codeDocument.GetCSharpDocument();
// Normalize newlines to match those in the baseline.
var actualCode = document.GeneratedCode.Replace("\r", "").Replace("\n", "\r\n");
var baselineFilePath = GetBaselineFilePath(codeDocument, ".codegen.cs");
var baselineDiagnosticsFilePath = GetBaselineFilePath(codeDocument, ".diagnostics.txt");
var baselineMappingsFilePath = GetBaselineFilePath(codeDocument, ".mappings.txt");
var serializedMappings = SourceMappingsSerializer.Serialize(document, codeDocument.Source);
if (GenerateBaselines)
{
var baselineFullPath = Path.Combine(TestProjectRoot, baselineFilePath);
Directory.CreateDirectory(Path.GetDirectoryName(baselineFullPath));
WriteBaseline(actualCode, baselineFullPath);
var baselineDiagnosticsFullPath = Path.Combine(TestProjectRoot, baselineDiagnosticsFilePath);
var lines = document.Diagnostics.Select(RazorDiagnosticSerializer.Serialize).ToArray();
if (lines.Any())
{
WriteBaseline(lines, baselineDiagnosticsFullPath);
}
else if (File.Exists(baselineDiagnosticsFullPath))
{
File.Delete(baselineDiagnosticsFullPath);
}
var baselineMappingsFullPath = Path.Combine(TestProjectRoot, baselineMappingsFilePath);
var text = SourceMappingsSerializer.Serialize(document, codeDocument.Source);
if (!string.IsNullOrEmpty(text))
{
WriteBaseline(text, baselineMappingsFullPath);
}
else if (File.Exists(baselineMappingsFullPath))
{
File.Delete(baselineMappingsFullPath);
}
return;
}
var codegenFile = TestFile.Create(baselineFilePath, GetType().Assembly);
if (!codegenFile.Exists())
{
throw new XunitException($"The resource {baselineFilePath} was not found.");
}
var baseline = codegenFile.ReadAllText();
Assert.Equal(baseline, actualCode);
var baselineDiagnostics = string.Empty;
var diagnosticsFile = TestFile.Create(baselineDiagnosticsFilePath, GetType().Assembly);
if (diagnosticsFile.Exists())
{
baselineDiagnostics = diagnosticsFile.ReadAllText();
}
var actualDiagnostics = string.Concat(document.Diagnostics.Select(d => RazorDiagnosticSerializer.Serialize(d) + "\r\n"));
Assert.Equal(baselineDiagnostics, actualDiagnostics);
var baselineMappings = string.Empty;
var mappingsFile = TestFile.Create(baselineMappingsFilePath, GetType().Assembly);
if (mappingsFile.Exists())
{
baselineMappings = mappingsFile.ReadAllText();
}
var actualMappings = SourceMappingsSerializer.Serialize(document, codeDocument.Source);
actualMappings = actualMappings.Replace("\r", "").Replace("\n", "\r\n");
Assert.Equal(baselineMappings, actualMappings);
}
private string GetBaselineFilePath(RazorCodeDocument codeDocument, string extension)
{
if (codeDocument == null)
{
throw new ArgumentNullException(nameof(codeDocument));
}
if (extension == null)
{
throw new ArgumentNullException(nameof(extension));
}
var lastSlash = codeDocument.Source.FilePath.LastIndexOfAny(new []{ '/', '\\' });
var fileName = lastSlash == -1 ? null : codeDocument.Source.FilePath.Substring(lastSlash + 1);
if (string.IsNullOrEmpty(fileName))
{
var message = "Integration tests require a filename";
throw new InvalidOperationException(message);
}
if (DirectoryPath == null)
{
var message = $"{nameof(AssertDocumentNodeMatchesBaseline)} should only be called from an integration test..";
throw new InvalidOperationException(message);
}
return Path.Combine(DirectoryPath, Path.ChangeExtension(fileName, extension));
}
private static void WriteBaseline(string text, string filePath)
{
var lines = text.Replace("\r", "").Replace("\n", "\r\n");
File.WriteAllText(filePath, text);
}
private static void WriteBaseline(string[] lines, string filePath)
{
using (var writer = new StreamWriter(File.Open(filePath, FileMode.Create)))
{
// Force windows-style line endings so that we're consistent. This isn't
// required for correctness, but will prevent churn when developing on OSX.
writer.NewLine = "\r\n";
for (var i = 0; i < lines.Length; i++)
{
writer.WriteLine(lines[i]);
}
}
}
}
}

View File

@ -8,9 +8,8 @@ using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Razor;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
@ -20,12 +19,15 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Razor;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Components.Build.Test
{
public class RazorIntegrationTestBase
{
private static readonly AsyncLocal<ITestOutputHelper> _output = new AsyncLocal<ITestOutputHelper>();
internal const string ArbitraryWindowsPath = "x:\\dir\\subdir\\Test";
internal const string ArbitraryMacLinuxPath = "/dir/subdir/Test";
@ -59,17 +61,23 @@ namespace Microsoft.AspNetCore.Components.Build.Test
CSharpParseOptions = new CSharpParseOptions(LanguageVersion.CSharp7_3);
}
public RazorIntegrationTestBase()
public RazorIntegrationTestBase(ITestOutputHelper output)
{
_output.Value = output;
AdditionalSyntaxTrees = new List<SyntaxTree>();
AdditionalRazorItems = new List<RazorProjectItem>();
Configuration = BlazorExtensionInitializer.DefaultConfiguration;
Configuration = RazorConfiguration.Create(RazorLanguageVersion.Latest, "MVC-3.0", Array.Empty<RazorExtension>());
FileKind = FileKinds.Component; // Treat input files as components by default.
FileSystem = new VirtualRazorProjectFileSystem();
PathSeparator = Path.DirectorySeparatorChar.ToString();
WorkingDirectory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ArbitraryWindowsPath : ArbitraryMacLinuxPath;
// Many of the rendering tests include line endings in the output.
LineEnding = "\n";
NormalizeSourceLineEndings = true;
DefaultBaseNamespace = "Test"; // Matches the default working directory
DefaultFileName = "TestComponent.cshtml";
}
@ -85,6 +93,8 @@ namespace Microsoft.AspNetCore.Components.Build.Test
internal virtual string DefaultFileName { get; }
internal virtual bool DesignTime { get; }
internal virtual string FileKind { get; }
internal virtual VirtualRazorProjectFileSystem FileSystem { get; }
@ -97,14 +107,15 @@ namespace Microsoft.AspNetCore.Components.Build.Test
internal virtual string PathSeparator { get; }
internal virtual bool NormalizeSourceLineEndings { get; }
internal virtual bool UseTwoPhaseCompilation { get; }
internal virtual string WorkingDirectory { get; }
internal RazorProjectEngine CreateProjectEngine(RazorConfiguration configuration, MetadataReference[] references)
// Intentionally private, we don't want tests messing with this because it's fragile.
private RazorProjectEngine CreateProjectEngine(MetadataReference[] references)
{
return RazorProjectEngine.Create(configuration, FileSystem, b =>
return RazorProjectEngine.Create(Configuration, FileSystem, b =>
{
// Turn off checksums, we're testing code generation.
b.Features.Add(new SuppressChecksum());
@ -114,7 +125,11 @@ namespace Microsoft.AspNetCore.Components.Build.Test
b.Phases.Insert(0, new ForceLineEndingPhase(LineEnding));
}
BlazorExtensionInitializer.Register(b);
// Including MVC here so that we can find any issues that arise from mixed MVC + Components.
Microsoft.AspNetCore.Mvc.Razor.Extensions.RazorExtensions.Register(b);
// Features that use Roslyn are mandatory for components
Microsoft.CodeAnalysis.Razor.CompilerFeatures.Register(b);
b.Features.Add(new CompilationTagHelperFeature());
b.Features.Add(new DefaultMetadataReferenceFeature()
@ -141,10 +156,11 @@ namespace Microsoft.AspNetCore.Components.Build.Test
}
return new VirtualProjectItem(
WorkingDirectory,
filePath,
WorkingDirectory,
filePath,
fullPath,
cshtmlRelativePath,
FileKind,
Encoding.UTF8.GetBytes(cshtmlContent.TrimStart()));
}
@ -159,13 +175,13 @@ namespace Microsoft.AspNetCore.Components.Build.Test
{
// The first phase won't include any metadata references for component discovery. This mirrors
// what the build does.
var projectEngine = CreateProjectEngine(BlazorExtensionInitializer.DeclarationConfiguration, Array.Empty<MetadataReference>());
var projectEngine = CreateProjectEngine(Array.Empty<MetadataReference>());
RazorCodeDocument codeDocument;
foreach (var item in AdditionalRazorItems)
{
// Result of generating declarations
codeDocument = projectEngine.Process(item);
codeDocument = projectEngine.ProcessDeclarationOnly(item);
Assert.Empty(codeDocument.GetCSharpDocument().Diagnostics);
var syntaxTree = Parse(codeDocument.GetCSharpDocument().GeneratedCode, path: item.FilePath);
@ -174,7 +190,7 @@ namespace Microsoft.AspNetCore.Components.Build.Test
// Result of generating declarations
var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent);
codeDocument = projectEngine.Process(projectItem);
codeDocument = projectEngine.ProcessDeclarationOnly(projectItem);
var declaration = new CompileToCSharpResult
{
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
@ -188,13 +204,13 @@ namespace Microsoft.AspNetCore.Components.Build.Test
// Add the 'temp' compilation as a metadata reference
var references = BaseCompilation.References.Concat(new[] { tempAssembly.Compilation.ToMetadataReference() }).ToArray();
projectEngine = CreateProjectEngine(BlazorExtensionInitializer.DefaultConfiguration, references);
projectEngine = CreateProjectEngine(references);
// Now update the any additional files
foreach (var item in AdditionalRazorItems)
{
// Result of generating declarations
codeDocument = projectEngine.Process(item);
codeDocument = DesignTime ? projectEngine.ProcessDesignTime(item) : projectEngine.Process(item);
Assert.Empty(codeDocument.GetCSharpDocument().Diagnostics);
// Replace the 'declaration' syntax tree
@ -205,6 +221,30 @@ namespace Microsoft.AspNetCore.Components.Build.Test
// Result of real code generation for the document under test
codeDocument = DesignTime ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem);
_output.Value.WriteLine("Use this output when opening an issue");
_output.Value.WriteLine(string.Empty);
_output.Value.WriteLine($"## Main source file ({projectItem.FileKind}):");
_output.Value.WriteLine("```");
_output.Value.WriteLine(ReadProjectItem(projectItem));
_output.Value.WriteLine("```");
_output.Value.WriteLine(string.Empty);
foreach (var item in AdditionalRazorItems)
{
_output.Value.WriteLine($"### Additional source file ({item.FileKind}):");
_output.Value.WriteLine("```");
_output.Value.WriteLine(ReadProjectItem(item));
_output.Value.WriteLine("```");
_output.Value.WriteLine(string.Empty);
}
_output.Value.WriteLine("## Generated C#:");
_output.Value.WriteLine("```C#");
_output.Value.WriteLine(codeDocument.GetCSharpDocument().GeneratedCode);
_output.Value.WriteLine("```");
return new CompileToCSharpResult
{
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
@ -217,10 +257,26 @@ namespace Microsoft.AspNetCore.Components.Build.Test
{
// For single phase compilation tests just use the base compilation's references.
// This will include the built-in Blazor components.
var projectEngine = CreateProjectEngine(Configuration, BaseCompilation.References.ToArray());
var projectEngine = CreateProjectEngine(BaseCompilation.References.ToArray());
var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent);
var codeDocument = DesignTime ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem);
// Log the generated code for test results.
_output.Value.WriteLine("Use this output when opening an issue");
_output.Value.WriteLine(string.Empty);
_output.Value.WriteLine($"## Main source file ({projectItem.FileKind}):");
_output.Value.WriteLine("```");
_output.Value.WriteLine(ReadProjectItem(projectItem));
_output.Value.WriteLine("```");
_output.Value.WriteLine(string.Empty);
_output.Value.WriteLine("## Generated C#:");
_output.Value.WriteLine("```");
_output.Value.WriteLine(codeDocument.GetCSharpDocument().GeneratedCode);
_output.Value.WriteLine("```");
return new CompileToCSharpResult
{
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
@ -343,6 +399,14 @@ namespace Microsoft.AspNetCore.Components.Build.Test
Assert.Equal(expected, generated.Code.Trim(), ignoreLineEndingDifferences: true);
}
private static string ReadProjectItem(RazorProjectItem item)
{
using (var reader = new StreamReader(item.Read()))
{
return reader.ReadToEnd();
}
}
protected class CompileToCSharpResult
{
// A compilation that can be used *with* this code to compile an assembly

View File

@ -3,10 +3,10 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.Build.Test
{
@ -14,6 +14,11 @@ namespace Microsoft.AspNetCore.Components.Build.Test
// Includes running the component code to verify the output.
public class RenderingRazorIntegrationTest : RazorIntegrationTestBase
{
public RenderingRazorIntegrationTest(ITestOutputHelper output)
: base(output)
{
}
[Fact]
public void SupportsPlainText()
{
@ -83,7 +88,7 @@ namespace Microsoft.AspNetCore.Components.Build.Test
frame => AssertFrame.Text(frame, "there", 2));
}
[Fact(Skip = "Temporarily disable compiling markup frames in 0.5.1")]
[Fact]
public void SupportsElementsAsStaticBlock()
{
// Arrange/Act
@ -144,28 +149,28 @@ namespace Microsoft.AspNetCore.Components.Build.Test
frame => AssertFrame.Attribute(frame, "myattr", "val", 2));
}
[Fact(Skip = "Temporarily disable compiling markup frames in 0.5.1")]
[Fact]
public void SupportsSelfClosingElementsAsStaticBlock()
{
// Arrange/Act
var component = CompileToComponent("Some text so elem isn't at position 0 <input attr='123' />");
// Assert
Assert.Collection(GetRenderTree(component),
frame => AssertFrame.Text(frame, "Some text so elem isn't at position 0 ", 0),
frame => AssertFrame.Markup(frame, "<input attr=\"123\">", 1));
Assert.Collection(
GetRenderTree(component),
frame => AssertFrame.Markup(frame, "Some text so elem isn't at position 0 <input attr=\"123\">", 0));
}
[Fact(Skip = "Temporarily disable compiling markup frames in 0.5.1")]
[Fact]
public void SupportsVoidHtmlElements()
{
// Arrange/Act
var component = CompileToComponent("Some text so elem isn't at position 0 <img>");
// Assert
Assert.Collection(GetRenderTree(component),
frame => AssertFrame.Text(frame, "Some text so elem isn't at position 0 ", 0),
frame => AssertFrame.Markup(frame, "<img>", 1));
Assert.Collection(
GetRenderTree(component),
frame => AssertFrame.Markup(frame, "Some text so elem isn't at position 0 <img>", 0));
}
[Fact]

View File

@ -1,9 +0,0 @@
// 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.
namespace Microsoft.AspNetCore.Components.Build.Test
{
public class RuntimeCodeGenerationTest : CodeGenerationTestBase
{
}
}

View File

@ -1,125 +0,0 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Components.Build.Test
{
public class TemplateRazorIntegrationTest : RazorIntegrationTestBase
{
// Razor doesn't parse this as a template, we don't need much special handling for
// it because it will just be invalid in general.
[Fact]
public void Template_ImplicitExpressionInMarkupAttribute_CreatesDiagnostic()
{
// Arrange
// Act
var generated = CompileToCSharp(@"<div attr=""@<div></div>"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ1005", diagnostic.Id);
}
[Fact]
public void Template_ExplicitExpressionInMarkupAttribute_CreatesDiagnostic()
{
// Arrange
// Act
var generated = CompileToCSharp(@"<div attr=""@(@<div></div>)"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("BL9994", diagnostic.Id);
}
// Razor doesn't parse this as a template, we don't need much special handling for
// it because it will just be invalid in general.
[Fact]
public void Template_ImplicitExpressionInComponentAttribute_CreatesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
}
}
"));
// Act
var generated = CompileToCSharp(@"<MyComponent attr=""@<div></div>"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ1005", diagnostic.Id);
}
[Fact]
public void Template_ExplicitExpressionInComponentAttribute_CreatesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
}
}
"));
// Act
var generated = CompileToCSharp(@"<MyComponent attr=""@(@<div></div>)"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("BL9994", diagnostic.Id);
}
[Fact]
public void Template_ExplicitExpressionInRef_CreatesDiagnostic()
{
// Arrange
// Act
var generated = CompileToCSharp(@"<div ref=""@(@<div></div>)"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("BL9994", diagnostic.Id);
}
[Fact]
public void Template_ExplicitExpressionInBind_CreatesDiagnostic()
{
// Arrange
// Act
var generated = CompileToCSharp(@"<input type=""text"" bind=""@(@<div></div>)"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("BL9994", diagnostic.Id);
}
[Fact]
public void Template_ExplicitExpressionInEventHandler_CreatesDiagnostic()
{
// Arrange
// Act
var generated = CompileToCSharp(@"<input type=""text"" onchange=""@(@<div></div>)"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("BL9994", diagnostic.Id);
}
}
}

View File

@ -1,94 +0,0 @@
// 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.Linq;
using Xunit;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Components.Build.Test
{
// Similar to design time code generation tests, but goes a character at a time.
// Don't add many of these since they are slow - instead add features to existing
// tests here, and use these as smoke tests, not for detailed regression testing.
public class TypingTest : RazorIntegrationTestBase
{
internal override bool DesignTime => true;
internal override bool UseTwoPhaseCompilation => false;
[Fact]
public void DoSomeTyping()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter] int Value { get; set; }
[Parameter] Action<int> ValueChanged { get; set; }
[Parameter] string AnotherValue { get; set; }
}
public class ModelState
{
public Action<string> Bind(Func<string, string> func) => throw null;
}
}
"));
var text = @"
@addTagHelper *, TestAssembly
<div>
<MyComponent bind-Value=""myValue"" AnotherValue=""hi""/>
<input type=""text"" bind=""@this.ModelState.Bind(x => x)"" />
<button ref=""_button"" onsubmit=""@FormSubmitted"">Click me</button>
</div>
<MyComponent
IntProperty=""123""
BoolProperty=""true""
StringProperty=""My string""
ObjectProperty=""new SomeType()""/>
@functions {
Test.ModelState ModelState { get; set; }
}";
for (var i = 0; i <= text.Length; i++)
{
try
{
CompileToCSharp(text.Substring(0, i));
}
catch (Exception ex)
{
throw new XunitException($@"
Code generation failed on iteration {i} with source text:
{text.Substring(0, i)}
Exception:
{ex}
");
}
}
}
[Fact] // Regression test for #1068
public void Regression_1068()
{
// Arrange
// Act
var generated = CompileToCSharp(@"
<input type=""text"" bind="" />
@functions {
Test.ModelState ModelState { get; set; }
}
");
// Assert
}
}
}

View File

@ -1,41 +0,0 @@
// 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.IO;
using System.Runtime.InteropServices;
using Xunit;
namespace Microsoft.AspNetCore.Components.Build.Test
{
// Integration tests focused on file path handling for class/namespace names
public class WorkingDirectoryRazorIntegrationTest : RazorIntegrationTestBase
{
public WorkingDirectoryRazorIntegrationTest()
{
WorkingDirectory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ArbitraryWindowsPath : ArbitraryMacLinuxPath;
WorkingDirectory += "-Dir";
}
internal override string WorkingDirectory { get; }
[Theory]
[InlineData("ItemAtRoot.cs", "Test_Dir", "ItemAtRoot")]
[InlineData("Dir1\\MyFile.cs", "Test_Dir.Dir1", "MyFile")]
[InlineData("Dir1\\Dir2\\MyFile.cs", "Test_Dir.Dir1.Dir2", "MyFile")]
public void CreatesClassWithCorrectNameAndNamespace(string relativePath, string expectedNamespace, string expectedClassName)
{
// Arrange
relativePath = relativePath.Replace('\\', Path.DirectorySeparatorChar);
// Act
var result = CompileToAssembly(relativePath, "");
// Assert
Assert.Empty(result.Diagnostics);
var type = Assert.Single(result.Assembly.GetTypes());
Assert.Equal(expectedNamespace, type.Namespace);
Assert.Equal(expectedClassName, type.Name);
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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.Threading;

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;