Simplify instrumentation confirmations in `RazorPageExecutionInstrumentationTest`
- test class can now use the `MvcTestFixture` - #3139 part 3 of 3 - dump instrumentation data at end of `_Layout.cshtml` - include `FilePath` in display - compare against new `.html` resource nits: - normalize line endings to CRLF for consistency with other tests - add `InstrumentionData` to avoid `Tuple` - make `TestPageExecutionContext` class `private` - remove newly-unused actions and associated `.cshtml` files
This commit is contained in:
parent
dc32f8ae33
commit
a69a7a6940
|
|
@ -1,213 +1,43 @@
|
|||
// 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.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using RazorPageExecutionInstrumentationWebSite;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
{
|
||||
public class RazorPageExecutionInstrumentationTest
|
||||
public class RazorPageExecutionInstrumentationTest : IClassFixture<MvcTestFixture<Startup>>
|
||||
{
|
||||
private const string SiteName = nameof(RazorPageExecutionInstrumentationWebSite);
|
||||
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
|
||||
private readonly Action<IServiceCollection> _configureServices = new Startup().ConfigureServices;
|
||||
private static readonly Assembly _resourcesAssembly =
|
||||
typeof(RazorPageExecutionInstrumentationTest).GetTypeInfo().Assembly;
|
||||
|
||||
public static IEnumerable<object[]> InstrumentationData
|
||||
public RazorPageExecutionInstrumentationTest(MvcTestFixture<Startup> fixture)
|
||||
{
|
||||
get
|
||||
{
|
||||
var expected = @"<div>
|
||||
2147483647
|
||||
viewstart-content
|
||||
<p class=""Hello world"">
|
||||
page-content
|
||||
</p>
|
||||
</div>";
|
||||
|
||||
var expectedLineMappings = new[]
|
||||
{
|
||||
Tuple.Create(92, 16, false),
|
||||
Tuple.Create(108, 1, true),
|
||||
Tuple.Create(0, 2, true),
|
||||
Tuple.Create(2, 8, true),
|
||||
Tuple.Create(10, 16, false),
|
||||
Tuple.Create(26, 1, true),
|
||||
Tuple.Create(27, 19, true),
|
||||
Tuple.Create(0, 6, true),
|
||||
Tuple.Create(7, 12, false),
|
||||
Tuple.Create(19, 1, true),
|
||||
Tuple.Create(21, 12, false),
|
||||
Tuple.Create(33, 7, true),
|
||||
};
|
||||
|
||||
yield return new object[] { "FullPath", expected, expectedLineMappings };
|
||||
yield return new object[] { "ViewDiscoveryPath", expected, expectedLineMappings };
|
||||
|
||||
var expected2 = @"<div>
|
||||
2147483647
|
||||
viewstart-content
|
||||
view-with-partial-content
|
||||
<p class=""class"">partial-content</p>
|
||||
<p class=""class"">partial-content</p>
|
||||
</div>";
|
||||
var expectedLineMappings2 = new[]
|
||||
{
|
||||
Tuple.Create(92, 16, false),
|
||||
Tuple.Create(108, 1, true),
|
||||
Tuple.Create(0, 26, true),
|
||||
Tuple.Create(27, 39, false),
|
||||
// Html.PartialAsync()
|
||||
Tuple.Create(28, 2, true),
|
||||
Tuple.Create(30, 8, true),
|
||||
Tuple.Create(38, 4, false),
|
||||
Tuple.Create(42, 1, true),
|
||||
Tuple.Create(43, 20, true),
|
||||
Tuple.Create(66, 1, true),
|
||||
// Html.RenderPartial()
|
||||
Tuple.Create(28, 2, true),
|
||||
Tuple.Create(30, 8, true),
|
||||
Tuple.Create(38, 4, false),
|
||||
Tuple.Create(42, 1, true),
|
||||
Tuple.Create(43, 20, true),
|
||||
Tuple.Create(0, 6, true),
|
||||
Tuple.Create(7, 12, false),
|
||||
Tuple.Create(19, 1, true),
|
||||
Tuple.Create(21, 12, false),
|
||||
Tuple.Create(33, 7, true)
|
||||
};
|
||||
yield return new object[] { "ViewWithPartial", expected2, expectedLineMappings2 };
|
||||
}
|
||||
Client = fixture.Client;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InstrumentationData))]
|
||||
public async Task ViewsAreServedWithoutInstrumentationByDefault(
|
||||
string actionName,
|
||||
string expected,
|
||||
IEnumerable<Tuple<int, int, bool>> ignored)
|
||||
{
|
||||
// Arrange
|
||||
var context = new TestPageExecutionContext();
|
||||
var server = TestHelper.CreateServer(_app, SiteName, services =>
|
||||
{
|
||||
services.AddInstance(context);
|
||||
_configureServices(services);
|
||||
});
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var body = await client.GetStringAsync("http://localhost/Home/" + actionName);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true);
|
||||
Assert.Empty(context.Values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InstrumentationData))]
|
||||
public async Task ViewsAreInstrumentedWhenPageExecutionListenerFeatureIsEnabled(
|
||||
string actionName,
|
||||
string expected,
|
||||
IEnumerable<Tuple<int, int, bool>> expectedLineMappings)
|
||||
{
|
||||
// Arrange
|
||||
var context = new TestPageExecutionContext();
|
||||
var server = TestHelper.CreateServer(_app, SiteName, services =>
|
||||
{
|
||||
services.AddInstance(context);
|
||||
_configureServices(services);
|
||||
});
|
||||
var client = server.CreateClient();
|
||||
client.DefaultRequestHeaders.Add("ENABLE-RAZOR-INSTRUMENTATION", "true");
|
||||
|
||||
// Act
|
||||
var body = await client.GetStringAsync("http://localhost/Home/" + actionName);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true);
|
||||
Assert.Equal(expectedLineMappings, context.Values);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InstrumentationData))]
|
||||
public async Task ViewsCanSwitchFromRegularToInstrumented(
|
||||
string actionName,
|
||||
string expected,
|
||||
IEnumerable<Tuple<int, int, bool>> expectedLineMappings)
|
||||
{
|
||||
// Arrange - 1
|
||||
var context = new TestPageExecutionContext();
|
||||
var server = TestHelper.CreateServer(_app, SiteName, services =>
|
||||
{
|
||||
services.AddInstance(context);
|
||||
_configureServices(services);
|
||||
});
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act - 1
|
||||
var body = await client.GetStringAsync("http://localhost/Home/" + actionName);
|
||||
|
||||
// Assert - 1
|
||||
Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true);
|
||||
Assert.Empty(context.Values);
|
||||
|
||||
// Arrange - 2
|
||||
client.DefaultRequestHeaders.Add("ENABLE-RAZOR-INSTRUMENTATION", "true");
|
||||
|
||||
// Act - 2
|
||||
body = await client.GetStringAsync("http://localhost/Home/" + actionName);
|
||||
|
||||
// Assert - 2
|
||||
Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true);
|
||||
Assert.Equal(expectedLineMappings, context.Values);
|
||||
}
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[Fact]
|
||||
public async Task SwitchingFromNonInstrumentedToInstrumentedWorksForLayoutAndViewStarts()
|
||||
public async Task InstrumentedViews_RenderAsExpected()
|
||||
{
|
||||
// Arrange - 1
|
||||
var expectedLineMappings = new[]
|
||||
{
|
||||
Tuple.Create(92, 16, false),
|
||||
Tuple.Create(108, 1, true),
|
||||
Tuple.Create(0, 2, true),
|
||||
Tuple.Create(2, 8, true),
|
||||
Tuple.Create(10, 16, false),
|
||||
Tuple.Create(26, 1, true),
|
||||
Tuple.Create(27, 19, true),
|
||||
Tuple.Create(0, 6, true),
|
||||
Tuple.Create(7, 12, false),
|
||||
Tuple.Create(19, 1, true),
|
||||
Tuple.Create(21, 12, false),
|
||||
Tuple.Create(33, 7, true),
|
||||
};
|
||||
var context = new TestPageExecutionContext();
|
||||
var server = TestHelper.CreateServer(_app, SiteName, services =>
|
||||
{
|
||||
services.AddInstance(context);
|
||||
_configureServices(services);
|
||||
});
|
||||
var client = server.CreateClient();
|
||||
// Arrange
|
||||
var outputFile = "compiler/resources/RazorPageExecutionInstrumentationWebSite.Home.ViewWithPartial.html";
|
||||
var expectedContent =
|
||||
await ResourceFile.ReadResourceAsync(_resourcesAssembly, outputFile, sourceFile: false);
|
||||
|
||||
// Act - 1
|
||||
var body = await client.GetStringAsync("http://localhost/Home/FullPath");
|
||||
// Act
|
||||
var content = await Client.GetStringAsync("http://localhost/Home/ViewWithPartial");
|
||||
|
||||
// Assert - 1
|
||||
Assert.Empty(context.Values);
|
||||
|
||||
// Arrange - 2
|
||||
client.DefaultRequestHeaders.Add("ENABLE-RAZOR-INSTRUMENTATION", "true");
|
||||
|
||||
// Act - 2
|
||||
body = await client.GetStringAsync("http://localhost/Home/ViewDiscoveryPath");
|
||||
|
||||
// Assert - 2
|
||||
Assert.Equal(expectedLineMappings, context.Values);
|
||||
// Assert
|
||||
#if GENERATE_BASELINES
|
||||
ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, content);
|
||||
#else
|
||||
Assert.Equal(expectedContent, content, ignoreLineEndingDifferences: true);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<div>
|
||||
2147483647
|
||||
viewstart-content
|
||||
view-with-partial-content
|
||||
<p class="class">partial-content</p>
|
||||
<p class="class">partial-content</p>
|
||||
</div>
|
||||
<p>/Views/_ViewStart.cshtml: Non-literal at 96 contains 16 characters.</p>
|
||||
<p>/Views/_ViewStart.cshtml: Literal at 112 contains 2 characters.</p>
|
||||
<p>/Views/Home/ViewWithPartial.cshtml: Literal at 0 contains 27 characters.</p>
|
||||
<p>/Views/Home/ViewWithPartial.cshtml: Non-literal at 28 contains 39 characters.</p>
|
||||
<p>/Views/Home/_PartialView.cshtml: Literal at 31 contains 2 characters.</p>
|
||||
<p>/Views/Home/_PartialView.cshtml: Literal at 33 contains 8 characters.</p>
|
||||
<p>/Views/Home/_PartialView.cshtml: Non-literal at 41 contains 4 characters.</p>
|
||||
<p>/Views/Home/_PartialView.cshtml: Literal at 45 contains 1 characters.</p>
|
||||
<p>/Views/Home/_PartialView.cshtml: Literal at 46 contains 20 characters.</p>
|
||||
<p>/Views/Home/_PartialView.cshtml: Literal at 67 contains 2 characters.</p>
|
||||
<p>/Views/Home/_PartialView.cshtml: Literal at 31 contains 2 characters.</p>
|
||||
<p>/Views/Home/_PartialView.cshtml: Literal at 33 contains 8 characters.</p>
|
||||
<p>/Views/Home/_PartialView.cshtml: Non-literal at 41 contains 4 characters.</p>
|
||||
<p>/Views/Home/_PartialView.cshtml: Literal at 45 contains 1 characters.</p>
|
||||
<p>/Views/Home/_PartialView.cshtml: Literal at 46 contains 20 characters.</p>
|
||||
<p>/Views/_Layout.cshtml: Literal at 82 contains 7 characters.</p>
|
||||
<p>/Views/_Layout.cshtml: Non-literal at 90 contains 12 characters.</p>
|
||||
<p>/Views/_Layout.cshtml: Literal at 102 contains 2 characters.</p>
|
||||
<p>/Views/_Layout.cshtml: Non-literal at 105 contains 12 characters.</p>
|
||||
<p>/Views/_Layout.cshtml: Literal at 117 contains 10 characters.</p>
|
||||
|
|
@ -7,16 +7,6 @@ namespace RazorPageExecutionInstrumentationWebSite
|
|||
{
|
||||
public class HomeController : Controller
|
||||
{
|
||||
public ActionResult FullPath()
|
||||
{
|
||||
return View("/Views/Home/FullPath.cshtml");
|
||||
}
|
||||
|
||||
public ActionResult ViewDiscoveryPath()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
public ActionResult ViewWithPartial()
|
||||
{
|
||||
return View();
|
||||
|
|
|
|||
|
|
@ -0,0 +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 System.Collections.Generic;
|
||||
|
||||
namespace RazorPageExecutionInstrumentationWebSite
|
||||
{
|
||||
public interface IHoldInstrumentationData
|
||||
{
|
||||
IEnumerable<InstrumentationData> Values { get; }
|
||||
|
||||
void IgnoreFurtherData();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// 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 RazorPageExecutionInstrumentationWebSite
|
||||
{
|
||||
public class InstrumentationData
|
||||
{
|
||||
public InstrumentationData(string filePath, int position, int length, bool isLiteral)
|
||||
{
|
||||
FilePath = filePath;
|
||||
Position = position;
|
||||
Length = length;
|
||||
IsLiteral = isLiteral;
|
||||
}
|
||||
|
||||
public string FilePath { get; }
|
||||
|
||||
public int Position { get; }
|
||||
|
||||
public int Length { get; }
|
||||
|
||||
public bool IsLiteral { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var literal = IsLiteral ? "Literal" : "Non-literal";
|
||||
|
||||
return $"{FilePath}: {literal} at {Position} contains {Length} characters.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ using Microsoft.AspNet.Http.Features;
|
|||
using Microsoft.AspNet.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNet.PageExecutionInstrumentation;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace RazorPageExecutionInstrumentationWebSite
|
||||
{
|
||||
|
|
@ -19,26 +18,28 @@ namespace RazorPageExecutionInstrumentationWebSite
|
|||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Normalize line endings to avoid changes in instrumentation locations between systems.
|
||||
services.TryAdd(ServiceDescriptor.Transient<IRazorCompilationService, TestRazorCompilationService>());
|
||||
services.AddTransient<IRazorCompilationService, TestRazorCompilationService>();
|
||||
|
||||
// Add MVC services to the services container
|
||||
// Add MVC services to the services container.
|
||||
services.AddMvc();
|
||||
|
||||
// Make instrumentation data available in views.
|
||||
services.AddScoped<TestPageExecutionListenerFeature, TestPageExecutionListenerFeature>();
|
||||
services.AddScoped<IHoldInstrumentationData>(
|
||||
provider => provider.GetRequiredService<TestPageExecutionListenerFeature>().Holder);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseCultureReplacer();
|
||||
|
||||
app.Use(async (HttpContext context, Func<Task> next) =>
|
||||
// Execute views with instrumentation enabled.
|
||||
app.Use((HttpContext context, Func<Task> next) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(context.Request.Headers["ENABLE-RAZOR-INSTRUMENTATION"]))
|
||||
{
|
||||
var pageExecutionContext = context.ApplicationServices.GetRequiredService<TestPageExecutionContext>();
|
||||
var listenerFeature = new TestPageExecutionListenerFeature(pageExecutionContext);
|
||||
context.Features.Set<IPageExecutionListenerFeature>(listenerFeature);
|
||||
}
|
||||
var listenerFeature = context.RequestServices.GetRequiredService<TestPageExecutionListenerFeature>();
|
||||
context.Features.Set<IPageExecutionListenerFeature>(listenerFeature);
|
||||
|
||||
await next();
|
||||
return next();
|
||||
});
|
||||
|
||||
// Add MVC to the request pipeline
|
||||
|
|
|
|||
|
|
@ -1,24 +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 Microsoft.AspNet.PageExecutionInstrumentation;
|
||||
|
||||
namespace RazorPageExecutionInstrumentationWebSite
|
||||
{
|
||||
public class TestPageExecutionContext : IPageExecutionContext
|
||||
{
|
||||
public List<Tuple<int, int, bool>> Values { get; }
|
||||
= new List<Tuple<int, int, bool>>();
|
||||
|
||||
public void BeginContext(int position, int length, bool isLiteral)
|
||||
{
|
||||
Values.Add(Tuple.Create(position, length, isLiteral));
|
||||
}
|
||||
|
||||
public void EndContext()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// 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.IO;
|
||||
using Microsoft.AspNet.PageExecutionInstrumentation;
|
||||
|
||||
|
|
@ -8,12 +9,9 @@ namespace RazorPageExecutionInstrumentationWebSite
|
|||
{
|
||||
public class TestPageExecutionListenerFeature : IPageExecutionListenerFeature
|
||||
{
|
||||
private readonly IPageExecutionContext _context;
|
||||
private readonly TestPageExecutionContext _executionContext = new TestPageExecutionContext();
|
||||
|
||||
public TestPageExecutionListenerFeature(IPageExecutionContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
public IHoldInstrumentationData Holder => _executionContext;
|
||||
|
||||
public TextWriter DecorateWriter(TextWriter writer)
|
||||
{
|
||||
|
|
@ -22,7 +20,38 @@ namespace RazorPageExecutionInstrumentationWebSite
|
|||
|
||||
public IPageExecutionContext GetContext(string sourceFilePath, TextWriter writer)
|
||||
{
|
||||
return _context;
|
||||
_executionContext.FilePath = sourceFilePath;
|
||||
|
||||
return _executionContext;
|
||||
}
|
||||
|
||||
private class TestPageExecutionContext : IHoldInstrumentationData, IPageExecutionContext
|
||||
{
|
||||
private readonly List<InstrumentationData> _values = new List<InstrumentationData>();
|
||||
private bool _ignoreNewData;
|
||||
|
||||
public string FilePath { get; set; }
|
||||
|
||||
public IEnumerable<InstrumentationData> Values => _values;
|
||||
|
||||
public void IgnoreFurtherData()
|
||||
{
|
||||
_ignoreNewData = true;
|
||||
}
|
||||
|
||||
public void BeginContext(int position, int length, bool isLiteral)
|
||||
{
|
||||
if (_ignoreNewData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_values.Add(new InstrumentationData(FilePath, position, length, isLiteral));
|
||||
}
|
||||
|
||||
public void EndContext()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,16 +22,13 @@ namespace RazorPageExecutionInstrumentationWebSite
|
|||
|
||||
protected override GeneratorResults GenerateCode(string relativePath, Stream inputStream)
|
||||
{
|
||||
// Normalize line endings to '\n' (LF). This removes core.autocrlf, core.eol, core.safecrlf, and
|
||||
// .gitattributes from the equation and treats "\r\n", "\r", and "\n" as equivalent. Does not handle
|
||||
// some obscure line endings (e.g. "\n\r") but otherwise ensures instrumentation locations are
|
||||
// consistent.
|
||||
// Normalize line endings to '\r\n' (CRLF). This removes core.autocrlf, core.eol, core.safecrlf, and
|
||||
// .gitattributes from the equation and treats "\r\n" and "\n" as equivalent. Does not handle
|
||||
// some line endings like "\r" but otherwise ensures checksums and line mappings are consistent.
|
||||
string text;
|
||||
using (var streamReader = new StreamReader(inputStream))
|
||||
{
|
||||
text = streamReader.ReadToEnd()
|
||||
.Replace("\r\n", "\n") // Windows line endings
|
||||
.Replace("\r", "\n"); // Older Mac OS line endings
|
||||
text = streamReader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n");
|
||||
}
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(text);
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
<p class="@("Hello world")">
|
||||
page-content
|
||||
</p>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<p class="@("Hello world")">
|
||||
page-content
|
||||
</p>
|
||||
|
|
@ -1,4 +1,12 @@
|
|||
<div>
|
||||
@inject RazorPageExecutionInstrumentationWebSite.IHoldInstrumentationData Holder
|
||||
<div>
|
||||
@int.MaxValue
|
||||
@RenderBody()
|
||||
</div>
|
||||
</div>
|
||||
@{
|
||||
Holder.IgnoreFurtherData();
|
||||
foreach (var data in Holder.Values)
|
||||
{
|
||||
<p>@data.ToString()</p>
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue