aspnetcore/test/Microsoft.AspNet.Mvc.Functi.../PrecompilationTest.cs

321 lines
14 KiB
C#

// 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.Net;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc.Razor.Precompilation;
using Microsoft.AspNet.Testing.xunit;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Runtime;
using PrecompilationWebSite;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class PrecompilationTest
{
private const string SiteName = nameof(PrecompilationWebSite);
private static readonly TimeSpan _cacheDelayInterval = TimeSpan.FromSeconds(1);
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
private readonly Action<IServiceCollection> _configureServices = new Startup().ConfigureServices;
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task PrecompiledView_RendersCorrectly()
{
// Arrange
IServiceCollection serviceCollection = null;
var server = TestHelper.CreateServer(_app, SiteName, services =>
{
_configureServices(services);
serviceCollection = services;
});
var client = server.CreateClient();
var serviceProvider = serviceCollection.BuildServiceProvider();
var applicationEnvironment = serviceProvider.GetRequiredService<IApplicationEnvironment>();
var viewsDirectory = Path.Combine(applicationEnvironment.ApplicationBasePath, "Views", "Home");
var layoutContent = File.ReadAllText(Path.Combine(viewsDirectory, "Layout.cshtml"));
var indexContent = File.ReadAllText(Path.Combine(viewsDirectory, "Index.cshtml"));
var viewstartContent = File.ReadAllText(Path.Combine(viewsDirectory, "_ViewStart.cshtml"));
var globalContent = File.ReadAllText(Path.Combine(viewsDirectory, "_ViewImports.cshtml"));
// We will render a view that writes the fully qualified name of the Assembly containing the type of
// the view. If the view is precompiled, this assembly will be PrecompilationWebsite.
var assemblyNamePrefix = GetAssemblyNamePrefix();
try
{
// Act - 1
var response = await client.GetAsync("http://localhost/Home/Index");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert - 1
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var parsedResponse1 = new ParsedResponse(responseContent);
Assert.StartsWith(assemblyNamePrefix, parsedResponse1.ViewStart);
Assert.StartsWith(assemblyNamePrefix, parsedResponse1.Layout);
Assert.StartsWith(assemblyNamePrefix, parsedResponse1.Index);
// Act - 2
// Touch the Layout file and verify it is now dynamically compiled.
await TouchFile(viewsDirectory, "Layout.cshtml");
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 2
var response2 = new ParsedResponse(responseContent);
Assert.StartsWith(assemblyNamePrefix, response2.ViewStart);
Assert.StartsWith(assemblyNamePrefix, response2.Index);
Assert.DoesNotContain(assemblyNamePrefix, response2.Layout);
// Act - 3
// Touch the _ViewStart file and verify it is is dynamically compiled.
await TouchFile(viewsDirectory, "_ViewStart.cshtml");
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 3
var response3 = new ParsedResponse(responseContent);
Assert.NotEqual(assemblyNamePrefix, response3.ViewStart);
Assert.Equal(response2.Index, response3.Index);
Assert.Equal(response2.Layout, response3.Layout);
// Act - 4
// Touch the _ViewImports file and verify it causes all files to recompile.
await TouchFile(viewsDirectory, "_ViewImports.cshtml");
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 4
var response4 = new ParsedResponse(responseContent);
Assert.NotEqual(response3.ViewStart, response4.ViewStart);
Assert.NotEqual(response3.Index, response4.Index);
Assert.NotEqual(response3.Layout, response4.Layout);
// Act - 5
// Touch Index file and verify it is the only page that recompiles.
await TouchFile(viewsDirectory, "Index.cshtml");
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 5
var response5 = new ParsedResponse(responseContent);
// Layout and _ViewStart should not have changed.
Assert.Equal(response4.Layout, response5.Layout);
Assert.Equal(response4.ViewStart, response5.ViewStart);
Assert.NotEqual(response4.Index, response5.Index);
// Act - 6
// Touch the _ViewImports file. This time, we'll verify the Non-precompiled -> Non-precompiled workflow.
await TouchFile(viewsDirectory, "_ViewImports.cshtml");
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 6
var response6 = new ParsedResponse(responseContent);
// Everything should've recompiled.
Assert.NotEqual(response5.ViewStart, response6.ViewStart);
Assert.NotEqual(response5.Index, response6.Index);
Assert.NotEqual(response5.Layout, response6.Layout);
// Act - 7
// Add a new _ViewImports file
var newViewImports = await TouchFile(Path.GetDirectoryName(viewsDirectory), "_ViewImports.cshtml");
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 7
// Everything should've recompiled.
var response7 = new ParsedResponse(responseContent);
Assert.NotEqual(response6.ViewStart, response7.ViewStart);
Assert.NotEqual(response6.Index, response7.Index);
Assert.NotEqual(response6.Layout, response7.Layout);
// Act - 8
// Remove new _ViewImports file
File.Delete(newViewImports);
await Task.Delay(_cacheDelayInterval);
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 8
// Everything should've recompiled.
var response8 = new ParsedResponse(responseContent);
Assert.NotEqual(response7.ViewStart, response8.ViewStart);
Assert.NotEqual(response7.Index, response8.Index);
Assert.NotEqual(response7.Layout, response8.Layout);
// Act - 9
// Refetch and verify we get cached types
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 9
var response9 = new ParsedResponse(responseContent);
Assert.Equal(response8.ViewStart, response9.ViewStart);
Assert.Equal(response8.Index, response9.Index);
Assert.Equal(response8.Layout, response9.Layout);
}
finally
{
File.WriteAllText(Path.Combine(viewsDirectory, "Layout.cshtml"), layoutContent.TrimEnd(' '));
File.WriteAllText(Path.Combine(viewsDirectory, "Index.cshtml"), indexContent.TrimEnd(' '));
File.WriteAllText(Path.Combine(viewsDirectory, "_ViewStart.cshtml"), viewstartContent.TrimEnd(' '));
File.WriteAllText(Path.Combine(viewsDirectory, "_ViewImports.cshtml"), globalContent.TrimEnd(' '));
}
}
[Fact]
public async Task PrecompiledView_UsesCompilationOptionsFromApplication()
{
// Arrange
var assemblyNamePrefix = GetAssemblyNamePrefix();
#if DNX451
var expected =
@"Value set inside DNX451 " + assemblyNamePrefix;
#elif DNXCORE50
var expected =
@"Value set inside DNXCORE50 " + assemblyNamePrefix;
#endif
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/Home/PrecompiledViewsCanConsumeCompilationOptions");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.StartsWith(expected, responseContent.Trim());
}
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task DeletingPrecompiledGlobalFile_PriorToFirstRequestToAView_CausesViewToBeRecompiled()
{
// Arrange
var expected = GetAssemblyNamePrefix();
IServiceCollection serviceCollection = null;
var server = TestHelper.CreateServer(_app, SiteName, services =>
{
_configureServices(services);
serviceCollection = services;
});
var client = server.CreateClient();
var serviceProvider = serviceCollection.BuildServiceProvider();
var applicationEnvironment = serviceProvider.GetRequiredService<IApplicationEnvironment>();
var viewsDirectory = Path.Combine(applicationEnvironment.ApplicationBasePath,
"Views",
"ViewImportsDelete");
var globalPath = Path.Combine(viewsDirectory, "_ViewImports.cshtml");
var globalContent = File.ReadAllText(globalPath);
// Act - 1
// Query the Test view so we know the compiler cache gets populated.
var response = await client.GetStringAsync("/Test");
// Assert - 1
Assert.Equal("Test", response.Trim());
try
{
// Act - 2
File.Delete(globalPath);
var response2 = await client.GetStringAsync("http://localhost/Home/GlobalDeletedPriorToFirstRequest");
// Assert - 2
Assert.DoesNotContain(expected, response2.Trim());
}
finally
{
File.WriteAllText(globalPath, globalContent);
}
}
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task TagHelpersFromTheApplication_CanBeAdded()
{
// Arrange
var assemblyNamePrefix = GetAssemblyNamePrefix();
var expected =
@"<root data-root=""true""><input class=""form-control"" type=""number"" data-val=""true""" +
@" data-val-range=""The field Age must be between 10 and 100."" data-val-range-max=""100"" "+
@"data-val-range-min=""10"" data-val-required=""The Age field is required."" " +
@"id=""Age"" name=""Age"" value="""" /><a href="""">Back to List</a></root>";
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
// Act
var response = await client.GetStringAsync("http://localhost/TagHelpers/Add");
// Assert
var responseLines = response.Split(new[] { "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries);
Assert.StartsWith(assemblyNamePrefix, responseLines[0]);
Assert.Equal(expected, responseLines[1]);
}
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task TagHelpersFromTheApplication_CanBeRemoved()
{
// Arrange
var assemblyNamePrefix = GetAssemblyNamePrefix();
var expected = @"<root>root-content</root>";
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
// Act
var response = await client.GetStringAsync("http://localhost/TagHelpers/Remove");
// Assert
var responseLines = response.Split(new[] { "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries);
Assert.StartsWith(assemblyNamePrefix, responseLines[0]);
Assert.Equal(expected, responseLines[1]);
}
private static string GetAssemblyNamePrefix()
{
return typeof(Startup).GetTypeInfo().Assembly.GetName().Name + "." + nameof(RazorPreCompiler) + ".";
}
private static async Task<string> TouchFile(string viewsDir, string file)
{
var path = Path.Combine(viewsDir, file);
File.AppendAllText(path, " ");
// Delay to allow the file system watcher to catch up.
await Task.Delay(_cacheDelayInterval);
return path;
}
private sealed class ParsedResponse
{
public ParsedResponse(string responseContent)
{
var results = responseContent
.Split(new[] { "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.ToArray();
Assert.True(results[0].StartsWith("Layout:"));
Layout = results[0].Substring("Layout:".Length);
Assert.True(results[1].StartsWith("_viewstart:"));
ViewStart = results[1].Substring("_viewstart:".Length);
Assert.True(results[2].StartsWith("index:"));
Index = results[2].Substring("index:".Length);
}
public string Layout { get; }
public string ViewStart { get; }
public string Index { get; }
}
}
}