* Redesign CompilationResult so that it does not throw when CompiledType is

accessed.
* Update to use ICompilationException interface from
  Microsoft.Framework.Runtime
* Update to use RoslynCompilationException

Fixes #955
This commit is contained in:
Pranav K 2015-02-19 17:16:53 -08:00 committed by Doug Bunting
parent 1d578ca2fd
commit 717c2bfd39
16 changed files with 359 additions and 279 deletions

View File

@ -3,13 +3,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNet.Diagnostics; using System.Linq;
using Microsoft.Framework.Runtime;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.Razor namespace Microsoft.AspNet.Mvc.Razor
{ {
/// <summary> /// <summary>
/// An exception thrown when accessing the result of a failed compilation. /// An <see cref="Exception"/> thrown when accessing the result of a failed compilation.
/// </summary> /// </summary>
public class CompilationFailedException : Exception, ICompilationException public class CompilationFailedException : Exception, ICompilationException
{ {
@ -20,12 +21,19 @@ namespace Microsoft.AspNet.Mvc.Razor
/// details of the compilation failure.</param> /// details of the compilation failure.</param>
public CompilationFailedException( public CompilationFailedException(
[NotNull] ICompilationFailure compilationFailure) [NotNull] ICompilationFailure compilationFailure)
: base(Resources.FormatCompilationFailed(compilationFailure.SourceFilePath)) : base(FormatMessage(compilationFailure))
{ {
CompilationFailures = new[] { compilationFailure }; CompilationFailures = new[] { compilationFailure };
} }
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<ICompilationFailure> CompilationFailures { get; } public IEnumerable<ICompilationFailure> CompilationFailures { get; }
private static string FormatMessage(ICompilationFailure compilationFailure)
{
return Resources.CompilationFailed +
Environment.NewLine +
string.Join(Environment.NewLine, compilationFailure.Messages.Select(message => message.FormattedMessage));
}
} }
} }

View File

@ -1,53 +0,0 @@
// 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.Collections.Generic;
using Microsoft.AspNet.Diagnostics;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Default implementation of <see cref="ICompilationFailure"/>.
/// </summary>
public class CompilationFailure : ICompilationFailure
{
/// <summary>Initializes a new instance of <see cref="CompilationFailure"/>.</summary>
/// <param name="filePath">The path of the Razor source file that was compiled.</param>
/// <param name="fileContent">The contents of the Razor source file.</param>
/// <param name="compiledContent">The generated C# content that was compiled.</param>
/// <param name="messages">A sequence of <see cref="ICompilationMessage"/> encountered
/// during compilation.</param>
public CompilationFailure(
[NotNull] string filePath,
[NotNull] string fileContent,
[NotNull] string compiledContent,
[NotNull] IEnumerable<ICompilationMessage> messages)
{
SourceFilePath = filePath;
SourceFileContent = fileContent;
Messages = messages;
CompiledContent = compiledContent;
}
/// <summary>
/// Gets the path of the Razor source file that produced the compilation failure.
/// </summary>
public string SourceFilePath { get; }
/// <summary>
/// Gets the content of the Razor source file.
/// </summary>
public string SourceFileContent { get; }
/// <summary>
/// Gets the generated C# content that was compiled.
/// </summary>
public string CompiledContent { get; }
/// <summary>
/// Gets a sequence of <see cref="ICompilationMessage"/> instances encountered during compilation.
/// </summary>
public IEnumerable<ICompilationMessage> Messages { get; }
}
}

View File

@ -1,57 +0,0 @@
// 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.AspNet.Diagnostics;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Represents a message encountered during compilation.
/// </summary>
public class CompilationMessage : ICompilationMessage
{
/// <summary>
/// Initializes a <see cref="CompilationMessage"/> with the specified message.
/// </summary>
/// <param name="message">A message <see cref="string"/> produced from compilation.</param>
public CompilationMessage(string message,
int startColumn,
int startLine,
int endColumn,
int endLine)
{
Message = message;
StartColumn = startColumn;
StartLine = startLine;
EndColumn = endColumn;
EndLine = endLine;
}
/// <summary>
/// Gets a message produced from compilation.
/// </summary>
public string Message { get; }
/// <inheritdoc />
public int StartColumn { get; }
/// <inheritdoc />
public int StartLine { get; }
/// <inheritdoc />
public int EndColumn { get; }
/// <inheritdoc />
public int EndLine { get; }
/// <summary>
/// Returns a <see cref="string"/> representation of this instance of <see cref="CompilationMessage"/>.
/// </summary>
/// <returns>A <see cref="string"/> representing this <see cref="CompilationMessage"/> instance.</returns>
/// <remarks>Returns same value as <see cref="Message"/>.</remarks>
public override string ToString()
{
return Message;
}
}
}

View File

@ -2,9 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic; using Microsoft.Framework.Runtime;
using System.IO;
using Microsoft.AspNet.FileProviders;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.Razor namespace Microsoft.AspNet.Mvc.Razor
@ -14,8 +12,6 @@ namespace Microsoft.AspNet.Mvc.Razor
/// </summary> /// </summary>
public class CompilationResult public class CompilationResult
{ {
private Type _type;
/// <summary> /// <summary>
/// Creates a new instance of <see cref="CompilationResult"/>. /// Creates a new instance of <see cref="CompilationResult"/>.
/// </summary> /// </summary>
@ -24,24 +20,10 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
/// <summary> /// <summary>
/// Gets the path of the Razor file that was compiled. /// Gets (or sets in derived types) the type produced as a result of compilation.
/// </summary> /// </summary>
public string FilePath /// <remarks>This property is <c>null</c> when compilation failed.</remarks>
{ public Type CompiledType { get; protected set; }
get
{
if (File != null)
{
return File.PhysicalPath;
}
return null;
}
}
/// <summary>
/// Gets a sequence of <see cref="CompilationMessage"/> instances encountered during compilation.
/// </summary>
public IEnumerable<CompilationMessage> Messages { get; private set; }
/// <summary> /// <summary>
/// Gets (or sets in derived types) the generated C# content that was compiled. /// Gets (or sets in derived types) the generated C# content that was compiled.
@ -49,52 +31,45 @@ namespace Microsoft.AspNet.Mvc.Razor
public string CompiledContent { get; protected set; } public string CompiledContent { get; protected set; }
/// <summary> /// <summary>
/// Gets (or sets in derived types) the type produced as a result of compilation. /// Gets the <see cref="ICompilationFailure"/> produced from parsing or compiling the Razor file.
/// </summary> /// </summary>
/// <exception cref="CompilationFailedException">An error occured during compilation.</exception> /// <remarks>This property is <c>null</c> when compilation succeeded.</remarks>
public Type CompiledType public ICompilationFailure CompilationFailure { get; private set; }
{
get
{
if (_type == null)
{
throw CreateCompilationFailedException();
}
return _type;
}
protected set
{
_type = value;
}
}
private IFileInfo File { get; set; }
/// <summary> /// <summary>
/// Creates a <see cref="CompilationResult"/> that represents a failure in compilation. /// Gets the <see cref="CompilationResult"/>.
/// </summary> /// </summary>
/// <param name="fileInfo">The <see cref="IFileInfo"/> for the Razor file that was compiled.</param> /// <returns>The current <see cref="CompilationResult"/> instance.</returns>
/// <param name="compilationContent">The generated C# content to be compiled.</param> /// <exception cref="CompilationFailedException">Thrown if compilation failed.</exception>
/// <param name="messages">The sequence of failure messages encountered during compilation.</param> public CompilationResult EnsureSuccessful()
/// <returns>A CompilationResult instance representing a failure.</returns> {
public static CompilationResult Failed([NotNull] IFileInfo file, if (CompilationFailure != null)
[NotNull] string compilationContent, {
[NotNull] IEnumerable<CompilationMessage> messages) throw new CompilationFailedException(CompilationFailure);
}
return this;
}
/// <summary>
/// Creates a <see cref="CompilationResult"/> for a failed compilation.
/// </summary>
/// <param name="compilationFailure">The <see cref="ICompilationFailure"/> produced from parsing or
/// compiling the Razor file.</param>
/// <returns>A <see cref="CompilationResult"/> instance for a failed compilation.</returns>
public static CompilationResult Failed([NotNull] ICompilationFailure compilationFailure)
{ {
return new CompilationResult return new CompilationResult
{ {
File = file, CompilationFailure = compilationFailure
CompiledContent = compilationContent,
Messages = messages,
}; };
} }
/// <summary> /// <summary>
/// Creates a <see cref="CompilationResult"/> that represents a success in compilation. /// Creates a <see cref="CompilationResult"/> for a successful compilation.
/// </summary> /// </summary>
/// <param name="type">The compiled type.</param> /// <param name="type">The compiled type.</param>
/// <returns>A CompilationResult instance representing a success.</returns> /// <returns>A <see cref="CompilationResult"/> instance for a successful compilation.</returns>
public static CompilationResult Successful([NotNull] Type type) public static CompilationResult Successful([NotNull] Type type)
{ {
return new CompilationResult return new CompilationResult
@ -102,31 +77,5 @@ namespace Microsoft.AspNet.Mvc.Razor
CompiledType = type CompiledType = type
}; };
} }
private CompilationFailedException CreateCompilationFailedException()
{
var fileContent = ReadContent(File);
var compilationFailure = new CompilationFailure(FilePath, fileContent, CompiledContent, Messages);
return new CompilationFailedException(compilationFailure);
}
private static string ReadContent(IFileInfo file)
{
try
{
using (var stream = file.CreateReadStream())
{
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
catch (Exception)
{
// Don't throw if reading the file fails.
return string.Empty;
}
}
} }
} }

View File

@ -180,7 +180,7 @@ namespace Microsoft.AspNet.Mvc.Razor
string normalizedPath, string normalizedPath,
Func<RelativeFileInfo, CompilationResult> compile) Func<RelativeFileInfo, CompilationResult> compile)
{ {
var compilationResult = compile(file); var compilationResult = compile(file).EnsureSuccessful();
// Concurrent addition to MemoryCache with the same key result in safe race. // Concurrent addition to MemoryCache with the same key result in safe race.
var cacheEntry = _cache.Set(normalizedPath, var cacheEntry = _cache.Set(normalizedPath,

View File

@ -1,8 +1,6 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.FileProviders;
namespace Microsoft.AspNet.Mvc.Razor namespace Microsoft.AspNet.Mvc.Razor
{ {
/// <summary> /// <summary>
@ -13,11 +11,11 @@ namespace Microsoft.AspNet.Mvc.Razor
/// <summary> /// <summary>
/// Compiles content and returns the result of compilation. /// Compiles content and returns the result of compilation.
/// </summary> /// </summary>
/// <param name="fileInfo">The <see cref="IFileInfo"/> for the Razor file that was compiled.</param> /// <param name="fileInfo">The <see cref="RelativeFileInfo"/> for the Razor file that was compiled.</param>
/// <param name="compilationContent">The generated C# content to be compiled.</param> /// <param name="compilationContent">The generated C# content to be compiled.</param>
/// <returns> /// <returns>
/// A <see cref="CompilationResult"/> representing the result of compilation. /// A <see cref="CompilationResult"/> representing the result of compilation.
/// </returns> /// </returns>
CompilationResult Compile(IFileInfo fileInfo, string compilationContent); CompilationResult Compile(RelativeFileInfo fileInfo, string compilationContent);
} }
} }

View File

@ -0,0 +1,42 @@
// 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.Collections.Generic;
using Microsoft.Framework.Runtime;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// <see cref="ICompilationFailure"/> for Razor parse failures.
/// </summary>
public class RazorCompilationFailure : ICompilationFailure
{
/// <summary>Initializes a new instance of <see cref="RazorCompilationFailure"/>.</summary>
/// <param name="sourceFilePath">The path of the Razor source file that was compiled.</param>
/// <param name="sourceFileContent">The contents of the Razor source file.</param>
/// <param name="messages">A sequence of <see cref="ICompilationMessage"/> encountered
/// during compilation.</param>
public RazorCompilationFailure(
[NotNull] string sourceFilePath,
[NotNull] string sourceFileContent,
[NotNull] IEnumerable<RazorCompilationMessage> messages)
{
SourceFilePath = sourceFilePath;
SourceFileContent = sourceFileContent;
Messages = messages;
}
/// <inheritdoc />
public string SourceFilePath { get; }
/// <inheritdoc />
public string SourceFileContent { get; }
/// <inheritdoc />
public string CompiledContent { get; } = null;
/// <inheritdoc />
public IEnumerable<ICompilationMessage> Messages { get; }
}
}

View File

@ -0,0 +1,74 @@
// 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.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Runtime;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// <see cref="ICompilationMessage"/> for a <see cref="RazorError"/> encountered during parsing.
/// </summary>
public class RazorCompilationMessage : ICompilationMessage
{
/// <summary>
/// Initializes a <see cref="RazorCompilationMessage"/> with the specified message.
/// </summary>
/// <param name="razorError">A <see cref="RazorError"/>.</param>
/// <param name="sourceFilePath">The path of the Razor source file that was parsed.</param>
public RazorCompilationMessage(
[NotNull] RazorError razorError,
string sourceFilePath)
{
SourceFilePath = sourceFilePath;
Message = razorError.Message;
var location = razorError.Location;
FormattedMessage =
$"{sourceFilePath} ({location.LineIndex},{location.CharacterIndex}) {razorError.Message}";
StartColumn = location.CharacterIndex;
StartLine = location.LineIndex;
EndColumn = location.CharacterIndex + razorError.Length;
EndLine = location.LineIndex;
}
/// <summary>
/// Gets a message produced from compilation.
/// </summary>
public string Message { get; }
/// <inheritdoc />
public int StartColumn { get; }
/// <inheritdoc />
public int StartLine { get; }
/// <inheritdoc />
public int EndColumn { get; }
/// <inheritdoc />
public int EndLine { get; }
/// <inheritdoc />
public string SourceFilePath { get; }
/// <inheritdoc />
public string FormattedMessage { get; }
/// <inheritdoc />
// All Razor diagnostics are errors
public CompilationMessageSeverity Severity { get; } = CompilationMessageSeverity.Error;
/// <summary>
/// Returns a <see cref="string"/> representation of this instance of <see cref="RazorCompilationMessage"/>.
/// </summary>
/// <returns>A <see cref="string"/> representing this <see cref="RazorCompilationMessage"/> instance.</returns>
/// <remarks>Returns same value as <see cref="Message"/>.</remarks>
public override string ToString()
{
return FormattedMessage;
}
}
}

View File

@ -60,12 +60,12 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
/// <inheritdoc /> /// <inheritdoc />
public CompilationResult Compile([NotNull] IFileInfo fileInfo, [NotNull] string compilationContent) public CompilationResult Compile([NotNull] RelativeFileInfo fileInfo, [NotNull] string compilationContent)
{ {
// The path passed to SyntaxTreeGenerator.Generate is used by the compiler to generate symbols (pdb) that // The path passed to SyntaxTreeGenerator.Generate is used by the compiler to generate symbols (pdb) that
// map to the source file. If a file does not exist on a physical file system, PhysicalPath will be null. // map to the source file. If a file does not exist on a physical file system, PhysicalPath will be null.
// This prevents files that exist in a non-physical file system from being debugged. // This prevents files that exist in a non-physical file system from being debugged.
var path = fileInfo.PhysicalPath ?? fileInfo.Name; var path = fileInfo.FileInfo.PhysicalPath ?? fileInfo.RelativePath;
var compilationSettings = _compilerOptionsProvider.GetCompilationSettings(_environment); var compilationSettings = _compilerOptionsProvider.GetCompilationSettings(_environment);
var syntaxTree = SyntaxTreeGenerator.Generate(compilationContent, var syntaxTree = SyntaxTreeGenerator.Generate(compilationContent,
path, path,
@ -98,14 +98,15 @@ namespace Microsoft.AspNet.Mvc.Razor
if (!result.Success) if (!result.Success)
{ {
var formatter = new DiagnosticFormatter(); var failures = result.Diagnostics.Where(IsError);
var compilationFailure = new RoslynCompilationFailure(failures)
{
CompiledContent = compilationContent,
SourceFileContent = ReadFileContentsSafely(fileInfo.FileInfo),
SourceFilePath = fileInfo.RelativePath
};
var messages = result.Diagnostics return CompilationResult.Failed(compilationFailure);
.Where(IsError)
.Select(d => GetCompilationMessage(formatter, d))
.ToList();
return CompilationResult.Failed(fileInfo, compilationContent, messages);
} }
Assembly assembly; Assembly assembly;
@ -215,21 +216,25 @@ namespace Microsoft.AspNet.Mvc.Razor
return metadata.GetReference(); return metadata.GetReference();
} }
private static CompilationMessage GetCompilationMessage(DiagnosticFormatter formatter, Diagnostic diagnostic)
{
var lineSpan = diagnostic.Location.GetMappedLineSpan();
return new CompilationMessage(formatter.Format(diagnostic),
startColumn: lineSpan.StartLinePosition.Character,
startLine: lineSpan.StartLinePosition.Line,
endColumn: lineSpan.EndLinePosition.Character,
endLine: lineSpan.EndLinePosition.Line);
}
private static bool IsError(Diagnostic diagnostic) private static bool IsError(Diagnostic diagnostic)
{ {
return diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error; return diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error;
} }
private static string ReadFileContentsSafely(IFileInfo fileInfo)
{
try
{
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
{
return reader.ReadToEnd();
}
}
catch
{
// Ignore any failures
return null;
}
}
} }
} }

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
/// <summary> /// <summary>
/// Error compiling page at '{0}'. /// One or more compilation failures occured:
/// </summary> /// </summary>
internal static string CompilationFailed internal static string CompilationFailed
{ {
@ -35,11 +35,11 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
/// <summary> /// <summary>
/// Error compiling page at '{0}'. /// One or more compilation failures occured:
/// </summary> /// </summary>
internal static string FormatCompilationFailed(object p0) internal static string FormatCompilationFailed()
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("CompilationFailed"), p0); return GetString("CompilationFailed");
} }
/// <summary> /// <summary>

View File

@ -1,7 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Linq; using System.Linq;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
@ -35,16 +37,32 @@ namespace Microsoft.AspNet.Mvc.Razor
if (!results.Success) if (!results.Success)
{ {
var messages = results.ParserErrors var messages = results.ParserErrors
.Select(parseError => .Select(parseError => new RazorCompilationMessage(parseError, file.RelativePath));
new CompilationMessage(parseError.Message, var failure = new RazorCompilationFailure(
parseError.Location.CharacterIndex, file.RelativePath,
parseError.Location.LineIndex, ReadFileContentsSafely(file.FileInfo),
parseError.Location.CharacterIndex + parseError.Length, messages);
parseError.Location.LineIndex));
return CompilationResult.Failed(file.FileInfo, results.GeneratedCode, messages); return CompilationResult.Failed(failure);
} }
return _compilationService.Compile(file.FileInfo, results.GeneratedCode); return _compilationService.Compile(file, results.GeneratedCode);
}
private static string ReadFileContentsSafely(IFileInfo fileInfo)
{
try
{
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
{
return reader.ReadToEnd();
}
}
catch
{
// Ignore any failures
return null;
}
} }
} }
} }

View File

@ -121,7 +121,7 @@
<value>Value cannot be null or empty.</value> <value>Value cannot be null or empty.</value>
</data> </data>
<data name="CompilationFailed" xml:space="preserve"> <data name="CompilationFailed" xml:space="preserve">
<value>Error compiling page at '{0}'.</value> <value>One or more compilation failures occured:</value>
</data> </data>
<data name="FlushPointCannotBeInvoked" xml:space="preserve"> <data name="FlushPointCannotBeInvoked" xml:space="preserve">
<value>'{0}' cannot be invoked when a Layout page is set to be executed.</value> <value>'{0}' cannot be invoked when a Layout page is set to be executed.</value>

View File

@ -1,9 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO; using Microsoft.Framework.Runtime;
using System.Text;
using Microsoft.AspNet.FileProviders;
using Moq; using Moq;
using Xunit; using Xunit;
@ -12,32 +10,18 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
public class CompilationResultTest public class CompilationResultTest
{ {
[Fact] [Fact]
public void FailedResult_ThrowsWhenAccessingCompiledType() public void EnsureSuccessful_ThrowsIfCompilationFailed()
{ {
// Arrange // Arrange
var expected = @"Error compiling page at 'myfile'."; var compilationFailure = Mock.Of<ICompilationFailure>();
var originalContent = "Original file content"; var result = CompilationResult.Failed(compilationFailure);
var fileInfo = new Mock<IFileInfo>();
fileInfo.SetupGet(f => f.PhysicalPath)
.Returns("myfile");
var contentBytes = Encoding.UTF8.GetBytes(originalContent);
fileInfo.Setup(f => f.CreateReadStream())
.Returns(new MemoryStream(contentBytes));
var messages = new[]
{
new CompilationMessage("hello", 1, 1, 2, 2),
new CompilationMessage("world", 3, 3, 4, 3)
};
var result = CompilationResult.Failed(fileInfo.Object,
"<h1>hello world</h1>",
messages);
// Act and Assert // Act and Assert
var ex = Assert.Throws<CompilationFailedException>(() => result.CompiledType); Assert.Null(result.CompiledType);
Assert.Equal(expected, ex.Message); Assert.Same(compilationFailure, result.CompilationFailure);
var compilationFailure = Assert.Single(ex.CompilationFailures); var exception = Assert.Throws<CompilationFailedException>(() => result.EnsureSuccessful());
Assert.Equal(originalContent, compilationFailure.SourceFileContent); Assert.Collection(exception.CompilationFailures,
Assert.Equal(messages, compilationFailure.Messages); failure => Assert.Same(compilationFailure, failure));
} }
} }
} }

View File

@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.Razor
// Assert // Assert
Assert.NotSame(CompilerCacheResult.FileNotFound, result); Assert.NotSame(CompilerCacheResult.FileNotFound, result);
var actual = result.CompilationResult; var actual = Assert.IsType<UncachedCompilationResult>(result.CompilationResult);
Assert.NotNull(actual); Assert.NotNull(actual);
Assert.Same(expected, actual); Assert.Same(expected, actual);
Assert.Equal("hello world", actual.CompiledContent); Assert.Equal("hello world", actual.CompiledContent);
@ -541,23 +541,18 @@ namespace Microsoft.AspNet.Mvc.Razor
// Act // Act
cache.GetOrAdd("test", _ => uncachedResult); cache.GetOrAdd("test", _ => uncachedResult);
var result1 = cache.GetOrAdd("test", _ => uncachedResult); var result1 = cache.GetOrAdd("test", _ => { throw new Exception("shouldn't be called."); });
var result2 = cache.GetOrAdd("test", _ => uncachedResult); var result2 = cache.GetOrAdd("test", _ => { throw new Exception("shouldn't be called."); });
// Assert // Assert
Assert.NotSame(CompilerCacheResult.FileNotFound, result1); Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
Assert.NotSame(CompilerCacheResult.FileNotFound, result2); Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
var actual1 = result1.CompilationResult; var actual1 = Assert.IsType<CompilationResult>(result1.CompilationResult);
var actual2 = result2.CompilationResult; var actual2 = Assert.IsType<CompilationResult>(result2.CompilationResult);
Assert.NotSame(uncachedResult, actual1); Assert.NotSame(uncachedResult, actual1);
Assert.NotSame(uncachedResult, actual2); Assert.NotSame(uncachedResult, actual2);
var result = Assert.IsType<CompilationResult>(actual1);
Assert.Null(actual1.CompiledContent);
Assert.Same(type, actual1.CompiledType); Assert.Same(type, actual1.CompiledType);
result = Assert.IsType<CompilationResult>(actual2);
Assert.Null(actual2.CompiledContent);
Assert.Same(type, actual2.CompiledType); Assert.Same(type, actual2.CompiledType);
} }

View File

@ -31,14 +31,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
fileInfo.Setup(f => f.PhysicalPath).Returns(viewPath); fileInfo.Setup(f => f.PhysicalPath).Returns(viewPath);
fileInfo.Setup(f => f.CreateReadStream()).Returns(Stream.Null); fileInfo.Setup(f => f.CreateReadStream()).Returns(Stream.Null);
var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml");
var compiler = new Mock<ICompilationService>(); var compiler = new Mock<ICompilationService>();
compiler.Setup(c => c.Compile(fileInfo.Object, It.IsAny<string>())) compiler.Setup(c => c.Compile(relativeFileInfo, It.IsAny<string>()))
.Returns(CompilationResult.Successful(typeof(RazorCompilationServiceTest))); .Returns(CompilationResult.Successful(typeof(RazorCompilationServiceTest)));
var razorService = new RazorCompilationService(compiler.Object, host.Object); var razorService = new RazorCompilationService(compiler.Object, host.Object);
var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml");
// Act // Act
razorService.Compile(relativeFileInfo); razorService.Compile(relativeFileInfo);
@ -75,9 +75,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
var result = razorService.Compile(relativeFileInfo); var result = razorService.Compile(relativeFileInfo);
// Assert // Assert
var ex = Assert.Throws<CompilationFailedException>(() => result.CompiledType); Assert.NotNull(result.CompilationFailure);
var failure = Assert.Single(ex.CompilationFailures); var message = Assert.Single(result.CompilationFailure.Messages);
var message = Assert.Single(failure.Messages);
Assert.Equal("some message", message.Message); Assert.Equal("some message", message.Message);
host.Verify(); host.Verify();
} }
@ -100,13 +99,13 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
var fileInfo = new Mock<IFileInfo>(); var fileInfo = new Mock<IFileInfo>();
fileInfo.Setup(f => f.CreateReadStream()) fileInfo.Setup(f => f.CreateReadStream())
.Returns(Stream.Null); .Returns(Stream.Null);
var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml");
var compilationResult = CompilationResult.Successful(typeof(object)); var compilationResult = CompilationResult.Successful(typeof(object));
var compiler = new Mock<ICompilationService>(); var compiler = new Mock<ICompilationService>();
compiler.Setup(c => c.Compile(fileInfo.Object, code)) compiler.Setup(c => c.Compile(relativeFileInfo, code))
.Returns(compilationResult) .Returns(compilationResult)
.Verifiable(); .Verifiable();
var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml");
var razorService = new RazorCompilationService(compiler.Object, host.Object); var razorService = new RazorCompilationService(compiler.Object, host.Object);
// Act // Act

View File

@ -5,6 +5,7 @@ using System;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using Microsoft.AspNet.FileProviders;
using Microsoft.Framework.Runtime; using Microsoft.Framework.Runtime;
using Moq; using Moq;
using Xunit; using Xunit;
@ -37,14 +38,126 @@ public class MyTestType {}";
libraryManager, libraryManager,
compilerOptionsProvider.Object, compilerOptionsProvider.Object,
mvcRazorHost.Object); mvcRazorHost.Object);
var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" },
"some-relative-path");
// Act // Act
var result = compilationService.Compile(new TestFileInfo { PhysicalPath = "SomePath" }, content); var result = compilationService.Compile(relativeFileInfo, content);
// Assert // Assert
var uncachedResult = Assert.IsType<UncachedCompilationResult>(result); var uncachedResult = Assert.IsType<UncachedCompilationResult>(result);
Assert.Equal("MyTestType", result.CompiledType.Name); Assert.Equal("MyTestType", result.CompiledType.Name);
Assert.Equal(content, result.CompiledContent); Assert.Equal(content, uncachedResult.CompiledContent);
}
[Fact]
public void Compile_ReturnsCompilationFailureWithRelativePath()
{
// Arrange
var fileContent = "test file content";
var content = @"this should fail";
var applicationEnvironment = GetApplicationEnvironment();
var accessor = GetLoadContextAccessor();
var libraryManager = GetLibraryManager();
var compilerOptionsProvider = new Mock<ICompilerOptionsProvider>();
compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationBasePath,
applicationEnvironment.RuntimeFramework,
applicationEnvironment.Configuration))
.Returns(new CompilerOptions());
var mvcRazorHost = Mock.Of<IMvcRazorHost>();
var compilationService = new RoslynCompilationService(applicationEnvironment,
accessor,
libraryManager,
compilerOptionsProvider.Object,
mvcRazorHost);
var fileInfo = new TestFileInfo
{
Content = fileContent,
PhysicalPath = "physical path"
};
var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path");
// Act
var result = compilationService.Compile(relativeFileInfo, content);
// Assert
Assert.IsType<CompilationResult>(result);
Assert.Null(result.CompiledType);
Assert.Equal(relativeFileInfo.RelativePath, result.CompilationFailure.SourceFilePath);
Assert.Equal(fileContent, result.CompilationFailure.SourceFileContent);
}
[Fact]
public void Compile_ReturnsApplicationRelativePath_IfPhyicalPathIsNotSpecified()
{
// Arrange
var fileContent = "file content";
var content = @"this should fail";
var applicationEnvironment = GetApplicationEnvironment();
var accessor = GetLoadContextAccessor();
var libraryManager = GetLibraryManager();
var compilerOptionsProvider = new Mock<ICompilerOptionsProvider>();
compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationBasePath,
applicationEnvironment.RuntimeFramework,
applicationEnvironment.Configuration))
.Returns(new CompilerOptions());
var mvcRazorHost = Mock.Of<IMvcRazorHost>();
var compilationService = new RoslynCompilationService(applicationEnvironment,
accessor,
libraryManager,
compilerOptionsProvider.Object,
mvcRazorHost);
var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { Content = fileContent },
"some-relative-path");
// Act
var result = compilationService.Compile(relativeFileInfo, content);
// Assert
Assert.IsType<CompilationResult>(result);
Assert.Null(result.CompiledType);
Assert.Equal("some-relative-path", result.CompilationFailure.SourceFilePath);
Assert.Equal(fileContent, result.CompilationFailure.SourceFileContent);
}
[Fact]
public void Compile_DoesNotThrow_IfFileCannotBeRead()
{
// Arrange
var content = @"this should fail";
var applicationEnvironment = GetApplicationEnvironment();
var accessor = GetLoadContextAccessor();
var libraryManager = GetLibraryManager();
var compilerOptionsProvider = new Mock<ICompilerOptionsProvider>();
compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationBasePath,
applicationEnvironment.RuntimeFramework,
applicationEnvironment.Configuration))
.Returns(new CompilerOptions());
var mvcRazorHost = Mock.Of<IMvcRazorHost>();
var compilationService = new RoslynCompilationService(applicationEnvironment,
accessor,
libraryManager,
compilerOptionsProvider.Object,
mvcRazorHost);
var mockFileInfo = new Mock<IFileInfo>();
mockFileInfo.Setup(f => f.CreateReadStream())
.Throws(new Exception());
var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, "some-relative-path");
// Act
var result = compilationService.Compile(relativeFileInfo, content);
// Assert
Assert.IsType<CompilationResult>(result);
Assert.Null(result.CompiledType);
Assert.Equal("some-relative-path", result.CompilationFailure.SourceFilePath);
Assert.Null(result.CompilationFailure.SourceFileContent);
} }
[Fact] [Fact]
@ -76,9 +189,11 @@ public class MyNonCustomDefinedClass {}
libraryManager, libraryManager,
compilerOptionsProvider.Object, compilerOptionsProvider.Object,
mvcRazorHost.Object); mvcRazorHost.Object);
var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" },
"some-relative-path");
// Act // Act
var result = compilationService.Compile(new TestFileInfo { PhysicalPath = "SomePath" }, content); var result = compilationService.Compile(relativeFileInfo, content);
// Assert // Assert
Assert.NotNull(result.CompiledType); Assert.NotNull(result.CompiledType);
@ -111,8 +226,11 @@ public class NotRazorPrefixType {}";
compilerOptionsProvider.Object, compilerOptionsProvider.Object,
mvcRazorHost.Object); mvcRazorHost.Object);
var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" },
"some-relative-path");
// Act // Act
var result = compilationService.Compile(new TestFileInfo { PhysicalPath = "SomePath" }, content); var result = compilationService.Compile(relativeFileInfo, content);
// Assert // Assert
Assert.NotNull(result.CompiledType); Assert.NotNull(result.CompiledType);