Add test to validate tooling path normalization.

- Tooling passes in rooted paths when asking for a RazorParser for a file. This was problematic when resolving inherited code trees and ultimately this commit tests that.

#2213
This commit is contained in:
N. Taylor Mullen 2015-03-21 13:57:02 -07:00
parent 570b1e583a
commit 9ac346529c
5 changed files with 124 additions and 32 deletions

View File

@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
/// <param name="pagePath">The path of the page to locate inherited chunks for.</param>
/// <returns>A <see cref="IReadOnlyList{CodeTree}"/> of parsed <c>_GlobalImport</c>
/// <see cref="CodeTree"/>s.</returns>
public IReadOnlyList<CodeTree> GetInheritedCodeTrees([NotNull] string pagePath)
public virtual IReadOnlyList<CodeTree> GetInheritedCodeTrees([NotNull] string pagePath)
{
var inheritedCodeTrees = new List<CodeTree>();
var templateEngine = new RazorTemplateEngine(_razorHost);

View File

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.Razor.Internal
{
public class DesignTimeRazorPathNormalizer : RazorPathNormalizer
{
private readonly string _applicationRoot;
public DesignTimeRazorPathNormalizer([NotNull] string applicationRoot)
{
_applicationRoot = applicationRoot;
}
public override string NormalizePath([NotNull] string path)
{
// Need to convert path to application relative (rooted paths are passed in during design time).
if (Path.IsPathRooted(path) && path.StartsWith(_applicationRoot, StringComparison.Ordinal))
{
path = path.Substring(_applicationRoot.Length);
}
return path;
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.Razor.Internal
{
public class RazorPathNormalizer
{
public virtual string NormalizePath([NotNull] string path)
{
return path;
}
}
}

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.IO;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Mvc.Razor.Directives;
using Microsoft.AspNet.Mvc.Razor.Internal;
using Microsoft.AspNet.Razor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Generator.Compiler;
@ -36,31 +37,13 @@ namespace Microsoft.AspNet.Mvc.Razor
// This field holds the type name without the generic decoration (MyBaseType)
private readonly string _baseType;
private readonly ICodeTreeCache _codeTreeCache;
private readonly RazorPathNormalizer _pathNormalizer;
private ChunkInheritanceUtility _chunkInheritanceUtility;
#if NET45
/// <summary>
/// Initializes a new instance of <see cref="MvcRazorHost"/> with the specified
/// <param name="root"/>.
/// </summary>
/// <param name="root">The path to the application base.</param>
// Note: This constructor is used by tooling and is created once for each
// Razor page that is loaded. Consequently, each loaded page has its own copy of
// the CodeTreeCache, but this ok - having a shared CodeTreeCache per application in tooling
// is problematic to manage.
public MvcRazorHost(string root) :
this(new DefaultCodeTreeCache(new PhysicalFileProvider(root)))
{
ApplicationRoot = root;
}
#endif
/// <summary>
/// Initializes a new instance of <see cref="MvcRazorHost"/> using the specified <paramref name="fileProvider"/>.
/// </summary>
/// <param name="fileProvider">A <see cref="IFileProvider"/> rooted at the application base path.</param>
public MvcRazorHost(ICodeTreeCache codeTreeCache)
internal MvcRazorHost(ICodeTreeCache codeTreeCache, RazorPathNormalizer pathNormalizer)
: base(new CSharpRazorCodeLanguage())
{
_pathNormalizer = pathNormalizer;
_baseType = BaseType;
_codeTreeCache = codeTreeCache;
@ -115,10 +98,26 @@ namespace Microsoft.AspNet.Mvc.Razor
#if NET45
/// <summary>
/// The path to the application root.
/// Initializes a new instance of <see cref="MvcRazorHost"/> with the specified <paramref name="root"/>.
/// </summary>
public string ApplicationRoot { get; }
/// <param name="root">The path to the application base.</param>
// Note: This constructor is used by tooling and is created once for each
// Razor page that is loaded. Consequently, each loaded page has its own copy of
// the CodeTreeCache, but this ok - having a shared CodeTreeCache per application in tooling
// is problematic to manage.
public MvcRazorHost(string root)
: this(new DefaultCodeTreeCache(new PhysicalFileProvider(root)), new DesignTimeRazorPathNormalizer(root))
{
}
#endif
/// <summary>
/// Initializes a new instance of <see cref="MvcRazorHost"/> using the specified <paramref name="codeTreeCache"/>.
/// </summary>
/// <param name="codeTreeCache">An <see cref="ICodeTreeCache"/> rooted at the application base path.</param>
public MvcRazorHost(ICodeTreeCache codeTreeCache)
: this(codeTreeCache, new RazorPathNormalizer())
{
}
/// <summary>
/// Gets the model type used by default when no model is specified.
@ -168,7 +167,8 @@ namespace Microsoft.AspNet.Mvc.Razor
get { return "CreateModelExpression"; }
}
private ChunkInheritanceUtility ChunkInheritanceUtility
// Internal for testing
internal ChunkInheritanceUtility ChunkInheritanceUtility
{
get
{
@ -180,6 +180,10 @@ namespace Microsoft.AspNet.Mvc.Razor
return _chunkInheritanceUtility;
}
set
{
_chunkInheritanceUtility = value;
}
}
/// <inheritdoc />
@ -194,13 +198,7 @@ namespace Microsoft.AspNet.Mvc.Razor
/// <inheritdoc />
public override RazorParser DecorateRazorParser([NotNull] RazorParser razorParser, string sourceFileName)
{
#if NET45
// Need to convert sourceFileName to application relative (rooted paths are passed in by tooling).
if (Path.IsPathRooted(sourceFileName))
{
sourceFileName = sourceFileName.Substring(ApplicationRoot.Length);
}
#endif
sourceFileName = _pathNormalizer.NormalizePath(sourceFileName);
var inheritedCodeTrees = ChunkInheritanceUtility.GetInheritedCodeTrees(sourceFileName);
return new MvcRazorParser(razorParser, inheritedCodeTrees, DefaultInheritedChunks);

View File

@ -1,20 +1,52 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.AspNet.Mvc.Razor.Directives;
using Microsoft.AspNet.Mvc.Razor.Internal;
using Microsoft.AspNet.Razor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Generator.Compiler;
using Microsoft.AspNet.Razor.Generator.Compiler.CSharp;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Text;
using Microsoft.Framework.Internal;
using Xunit;
namespace Microsoft.AspNet.Mvc.Razor
{
public class MvcRazorHostTest
{
[Theory]
[InlineData("//")]
[InlineData("C:/")]
[InlineData(@"\\")]
[InlineData(@"C:\")]
public void DecorateRazorParser_DesignTimeRazorPathNormalizer_NormalizesChunkInheritanceUtilityPaths(
string rootPrefix)
{
// Arrange
var rootedAppPath = $"{rootPrefix}SomeComputer/Location/Project/";
var rootedFilePath = $"{rootPrefix}SomeComputer/Location/Project/src/file.cshtml";
var host = new MvcRazorHost(
codeTreeCache: null,
pathNormalizer: new DesignTimeRazorPathNormalizer(rootedAppPath));
var parser = new RazorParser(
host.CodeLanguage.CreateCodeParser(),
host.CreateMarkupParser(),
tagHelperDescriptorResolver: null);
var chunkInheritanceUtility = new PathValidatingChunkInheritanceUtility(host);
host.ChunkInheritanceUtility = chunkInheritanceUtility;
// Act
host.DecorateRazorParser(parser, rootedFilePath);
// Assert
Assert.Equal("src/file.cshtml", chunkInheritanceUtility.InheritedCodeTreePagePath, StringComparer.Ordinal);
}
[Fact]
public void MvcRazorHost_EnablesInstrumentationByDefault()
{
@ -254,6 +286,23 @@ namespace Microsoft.AspNet.Mvc.Razor
generatedLocation: new MappingLocation(generatedLocation, contentLength));
}
private class PathValidatingChunkInheritanceUtility : ChunkInheritanceUtility
{
public PathValidatingChunkInheritanceUtility(MvcRazorHost razorHost)
: base(razorHost, codeTreeCache: null, defaultInheritedChunks: new Chunk[0])
{
}
public string InheritedCodeTreePagePath { get; private set; }
public override IReadOnlyList<CodeTree> GetInheritedCodeTrees([NotNull] string pagePath)
{
InheritedCodeTreePagePath = pagePath;
return new CodeTree[0];
}
}
/// <summary>
/// Used when testing Tag Helpers, it disables the unique ID generation feature.
/// </summary>