diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorPageExecutionInstrumentationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorPageExecutionInstrumentationTest.cs index 9a716a8f9f..75cd5d8829 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorPageExecutionInstrumentationTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorPageExecutionInstrumentationTest.cs @@ -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> { - private const string SiteName = nameof(RazorPageExecutionInstrumentationWebSite); - private readonly Action _app = new Startup().Configure; - private readonly Action _configureServices = new Startup().ConfigureServices; + private static readonly Assembly _resourcesAssembly = + typeof(RazorPageExecutionInstrumentationTest).GetTypeInfo().Assembly; - public static IEnumerable InstrumentationData + public RazorPageExecutionInstrumentationTest(MvcTestFixture fixture) { - get - { - var expected = @"
-2147483647 -viewstart-content -

-page-content -

-
"; - - 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 = @"
-2147483647 -viewstart-content -view-with-partial-content -

partial-content

-

partial-content

-
"; - 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> 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> 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> 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 } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/RazorPageExecutionInstrumentationWebSite.Home.ViewWithPartial.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/RazorPageExecutionInstrumentationWebSite.Home.ViewWithPartial.html new file mode 100644 index 0000000000..061fd827a1 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/RazorPageExecutionInstrumentationWebSite.Home.ViewWithPartial.html @@ -0,0 +1,27 @@ +
+2147483647 +viewstart-content +view-with-partial-content +

partial-content

+

partial-content

+
+

/Views/_ViewStart.cshtml: Non-literal at 96 contains 16 characters.

+

/Views/_ViewStart.cshtml: Literal at 112 contains 2 characters.

+

/Views/Home/ViewWithPartial.cshtml: Literal at 0 contains 27 characters.

+

/Views/Home/ViewWithPartial.cshtml: Non-literal at 28 contains 39 characters.

+

/Views/Home/_PartialView.cshtml: Literal at 31 contains 2 characters.

+

/Views/Home/_PartialView.cshtml: Literal at 33 contains 8 characters.

+

/Views/Home/_PartialView.cshtml: Non-literal at 41 contains 4 characters.

+

/Views/Home/_PartialView.cshtml: Literal at 45 contains 1 characters.

+

/Views/Home/_PartialView.cshtml: Literal at 46 contains 20 characters.

+

/Views/Home/_PartialView.cshtml: Literal at 67 contains 2 characters.

+

/Views/Home/_PartialView.cshtml: Literal at 31 contains 2 characters.

+

/Views/Home/_PartialView.cshtml: Literal at 33 contains 8 characters.

+

/Views/Home/_PartialView.cshtml: Non-literal at 41 contains 4 characters.

+

/Views/Home/_PartialView.cshtml: Literal at 45 contains 1 characters.

+

/Views/Home/_PartialView.cshtml: Literal at 46 contains 20 characters.

+

/Views/_Layout.cshtml: Literal at 82 contains 7 characters.

+

/Views/_Layout.cshtml: Non-literal at 90 contains 12 characters.

+

/Views/_Layout.cshtml: Literal at 102 contains 2 characters.

+

/Views/_Layout.cshtml: Non-literal at 105 contains 12 characters.

+

/Views/_Layout.cshtml: Literal at 117 contains 10 characters.

diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/HomeController.cs b/test/WebSites/RazorPageExecutionInstrumentationWebSite/HomeController.cs index afe1ea6c5c..92eb20dfd5 100644 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/HomeController.cs +++ b/test/WebSites/RazorPageExecutionInstrumentationWebSite/HomeController.cs @@ -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(); diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/IHoldInstrumentationData.cs b/test/WebSites/RazorPageExecutionInstrumentationWebSite/IHoldInstrumentationData.cs new file mode 100644 index 0000000000..67e90f7ce2 --- /dev/null +++ b/test/WebSites/RazorPageExecutionInstrumentationWebSite/IHoldInstrumentationData.cs @@ -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 Values { get; } + + void IgnoreFurtherData(); + } +} diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/InstrumentationData.cs b/test/WebSites/RazorPageExecutionInstrumentationWebSite/InstrumentationData.cs new file mode 100644 index 0000000000..a3de66fbb5 --- /dev/null +++ b/test/WebSites/RazorPageExecutionInstrumentationWebSite/InstrumentationData.cs @@ -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."; + } + } +} diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Startup.cs b/test/WebSites/RazorPageExecutionInstrumentationWebSite/Startup.cs index 36539786d8..8e26a5e9b1 100644 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Startup.cs +++ b/test/WebSites/RazorPageExecutionInstrumentationWebSite/Startup.cs @@ -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()); + services.AddTransient(); - // Add MVC services to the services container + // Add MVC services to the services container. services.AddMvc(); + + // Make instrumentation data available in views. + services.AddScoped(); + services.AddScoped( + provider => provider.GetRequiredService().Holder); } public void Configure(IApplicationBuilder app) { app.UseCultureReplacer(); - app.Use(async (HttpContext context, Func next) => + // Execute views with instrumentation enabled. + app.Use((HttpContext context, Func next) => { - if (!string.IsNullOrEmpty(context.Request.Headers["ENABLE-RAZOR-INSTRUMENTATION"])) - { - var pageExecutionContext = context.ApplicationServices.GetRequiredService(); - var listenerFeature = new TestPageExecutionListenerFeature(pageExecutionContext); - context.Features.Set(listenerFeature); - } + var listenerFeature = context.RequestServices.GetRequiredService(); + context.Features.Set(listenerFeature); - await next(); + return next(); }); // Add MVC to the request pipeline diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestPageExecutionContext.cs b/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestPageExecutionContext.cs deleted file mode 100644 index d8baa2438c..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestPageExecutionContext.cs +++ /dev/null @@ -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> Values { get; } - = new List>(); - - public void BeginContext(int position, int length, bool isLiteral) - { - Values.Add(Tuple.Create(position, length, isLiteral)); - } - - public void EndContext() - { - } - } -} \ No newline at end of file diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestPageExecutionListenerFeature.cs b/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestPageExecutionListenerFeature.cs index 6d3d4e544c..4d3d5482f8 100644 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestPageExecutionListenerFeature.cs +++ b/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestPageExecutionListenerFeature.cs @@ -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 _values = new List(); + private bool _ignoreNewData; + + public string FilePath { get; set; } + + public IEnumerable 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() + { + } } } } \ No newline at end of file diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestRazorCompilationService.cs b/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestRazorCompilationService.cs index cd566ccaf9..b595da02f2 100644 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestRazorCompilationService.cs +++ b/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestRazorCompilationService.cs @@ -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); diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/FullPath.cshtml b/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/FullPath.cshtml deleted file mode 100644 index 1622faf787..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/FullPath.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -

-page-content -

\ No newline at end of file diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/ViewDiscoveryPath.cshtml b/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/ViewDiscoveryPath.cshtml deleted file mode 100644 index 1622faf787..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/ViewDiscoveryPath.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -

-page-content -

\ No newline at end of file diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/_Layout.cshtml b/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/_Layout.cshtml index d8615a7d7a..9fd7ce212b 100644 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/_Layout.cshtml +++ b/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/_Layout.cshtml @@ -1,4 +1,12 @@ -
+@inject RazorPageExecutionInstrumentationWebSite.IHoldInstrumentationData Holder +
@int.MaxValue @RenderBody() -
\ No newline at end of file +
+@{ + Holder.IgnoreFurtherData(); + foreach (var data in Holder.Values) + { +

@data.ToString()

+ } +} \ No newline at end of file