From 717c2bfd393b236c4bf16dcd4e02ea9a598f8370 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 19 Feb 2015 17:16:53 -0800 Subject: [PATCH] * 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 --- .../Compilation/CompilationFailedException.cs | 14 +- .../Compilation/CompilationFailure.cs | 53 -------- .../Compilation/CompilationMessage.cs | 57 -------- .../Compilation/CompilationResult.cs | 111 +++++---------- .../Compilation/CompilerCache.cs | 2 +- .../Compilation/ICompilationService.cs | 6 +- .../Compilation/RazorCompilationFailure.cs | 42 ++++++ .../Compilation/RazorCompilationMessage.cs | 74 ++++++++++ .../Compilation/RoslynCompilationService.cs | 45 ++++--- .../Properties/Resources.Designer.cs | 8 +- .../Razor/RazorCompilationService.cs | 34 +++-- src/Microsoft.AspNet.Mvc.Razor/Resources.resx | 2 +- .../Compilation/CompilationResultTest.cs | 34 ++--- .../Compilation/CompilerCacheTest.cs | 15 +-- .../RazorCompilationServiceTest.cs | 15 +-- .../RoslynCompilationServiceTest.cs | 126 +++++++++++++++++- 16 files changed, 359 insertions(+), 279 deletions(-) delete mode 100644 src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationFailure.cs delete mode 100644 src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationMessage.cs create mode 100644 src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationFailure.cs create mode 100644 src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationMessage.cs diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationFailedException.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationFailedException.cs index 6c1530ad5a..5294bf8c5f 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationFailedException.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationFailedException.cs @@ -3,13 +3,14 @@ using System; using System.Collections.Generic; -using Microsoft.AspNet.Diagnostics; +using System.Linq; +using Microsoft.Framework.Runtime; using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.Razor { /// - /// An exception thrown when accessing the result of a failed compilation. + /// An thrown when accessing the result of a failed compilation. /// public class CompilationFailedException : Exception, ICompilationException { @@ -20,12 +21,19 @@ namespace Microsoft.AspNet.Mvc.Razor /// details of the compilation failure. public CompilationFailedException( [NotNull] ICompilationFailure compilationFailure) - : base(Resources.FormatCompilationFailed(compilationFailure.SourceFilePath)) + : base(FormatMessage(compilationFailure)) { CompilationFailures = new[] { compilationFailure }; } /// public IEnumerable CompilationFailures { get; } + + private static string FormatMessage(ICompilationFailure compilationFailure) + { + return Resources.CompilationFailed + + Environment.NewLine + + string.Join(Environment.NewLine, compilationFailure.Messages.Select(message => message.FormattedMessage)); + } } } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationFailure.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationFailure.cs deleted file mode 100644 index 28cf5dac8f..0000000000 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationFailure.cs +++ /dev/null @@ -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 -{ - /// - /// Default implementation of . - /// - public class CompilationFailure : ICompilationFailure - { - /// Initializes a new instance of . - /// The path of the Razor source file that was compiled. - /// The contents of the Razor source file. - /// The generated C# content that was compiled. - /// A sequence of encountered - /// during compilation. - public CompilationFailure( - [NotNull] string filePath, - [NotNull] string fileContent, - [NotNull] string compiledContent, - [NotNull] IEnumerable messages) - { - SourceFilePath = filePath; - SourceFileContent = fileContent; - Messages = messages; - CompiledContent = compiledContent; - } - - /// - /// Gets the path of the Razor source file that produced the compilation failure. - /// - public string SourceFilePath { get; } - - /// - /// Gets the content of the Razor source file. - /// - public string SourceFileContent { get; } - - /// - /// Gets the generated C# content that was compiled. - /// - public string CompiledContent { get; } - - /// - /// Gets a sequence of instances encountered during compilation. - /// - public IEnumerable Messages { get; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationMessage.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationMessage.cs deleted file mode 100644 index b0b6e615a2..0000000000 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationMessage.cs +++ /dev/null @@ -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 -{ - /// - /// Represents a message encountered during compilation. - /// - public class CompilationMessage : ICompilationMessage - { - /// - /// Initializes a with the specified message. - /// - /// A message produced from compilation. - public CompilationMessage(string message, - int startColumn, - int startLine, - int endColumn, - int endLine) - { - Message = message; - StartColumn = startColumn; - StartLine = startLine; - EndColumn = endColumn; - EndLine = endLine; - } - - /// - /// Gets a message produced from compilation. - /// - public string Message { get; } - - /// - public int StartColumn { get; } - - /// - public int StartLine { get; } - - /// - public int EndColumn { get; } - - /// - public int EndLine { get; } - - /// - /// Returns a representation of this instance of . - /// - /// A representing this instance. - /// Returns same value as . - public override string ToString() - { - return Message; - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs index ee4d1508d2..714d68404b 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs @@ -2,9 +2,7 @@ // 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.FileProviders; +using Microsoft.Framework.Runtime; using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.Razor @@ -14,8 +12,6 @@ namespace Microsoft.AspNet.Mvc.Razor /// public class CompilationResult { - private Type _type; - /// /// Creates a new instance of . /// @@ -24,24 +20,10 @@ namespace Microsoft.AspNet.Mvc.Razor } /// - /// Gets the path of the Razor file that was compiled. + /// Gets (or sets in derived types) the type produced as a result of compilation. /// - public string FilePath - { - get - { - if (File != null) - { - return File.PhysicalPath; - } - return null; - } - } - - /// - /// Gets a sequence of instances encountered during compilation. - /// - public IEnumerable Messages { get; private set; } + /// This property is null when compilation failed. + public Type CompiledType { get; protected set; } /// /// 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; } /// - /// Gets (or sets in derived types) the type produced as a result of compilation. + /// Gets the produced from parsing or compiling the Razor file. /// - /// An error occured during compilation. - public Type CompiledType - { - get - { - if (_type == null) - { - throw CreateCompilationFailedException(); - } - - return _type; - } - protected set - { - _type = value; - } - } - - private IFileInfo File { get; set; } + /// This property is null when compilation succeeded. + public ICompilationFailure CompilationFailure { get; private set; } /// - /// Creates a that represents a failure in compilation. + /// Gets the . /// - /// The for the Razor file that was compiled. - /// The generated C# content to be compiled. - /// The sequence of failure messages encountered during compilation. - /// A CompilationResult instance representing a failure. - public static CompilationResult Failed([NotNull] IFileInfo file, - [NotNull] string compilationContent, - [NotNull] IEnumerable messages) + /// The current instance. + /// Thrown if compilation failed. + public CompilationResult EnsureSuccessful() + { + if (CompilationFailure != null) + { + throw new CompilationFailedException(CompilationFailure); + } + + return this; + } + + /// + /// Creates a for a failed compilation. + /// + /// The produced from parsing or + /// compiling the Razor file. + /// A instance for a failed compilation. + public static CompilationResult Failed([NotNull] ICompilationFailure compilationFailure) { return new CompilationResult { - File = file, - CompiledContent = compilationContent, - Messages = messages, + CompilationFailure = compilationFailure }; } /// - /// Creates a that represents a success in compilation. + /// Creates a for a successful compilation. /// /// The compiled type. - /// A CompilationResult instance representing a success. + /// A instance for a successful compilation. public static CompilationResult Successful([NotNull] Type type) { return new CompilationResult @@ -102,31 +77,5 @@ namespace Microsoft.AspNet.Mvc.Razor 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; - } - } } } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs index a1c3971e17..8012e29e6c 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs @@ -180,7 +180,7 @@ namespace Microsoft.AspNet.Mvc.Razor string normalizedPath, Func compile) { - var compilationResult = compile(file); + var compilationResult = compile(file).EnsureSuccessful(); // Concurrent addition to MemoryCache with the same key result in safe race. var cacheEntry = _cache.Set(normalizedPath, diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilationService.cs index 24ea983e3d..1d88cf4009 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilationService.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilationService.cs @@ -1,8 +1,6 @@ // 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.FileProviders; - namespace Microsoft.AspNet.Mvc.Razor { /// @@ -13,11 +11,11 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// Compiles content and returns the result of compilation. /// - /// The for the Razor file that was compiled. + /// The for the Razor file that was compiled. /// The generated C# content to be compiled. /// /// A representing the result of compilation. /// - CompilationResult Compile(IFileInfo fileInfo, string compilationContent); + CompilationResult Compile(RelativeFileInfo fileInfo, string compilationContent); } } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationFailure.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationFailure.cs new file mode 100644 index 0000000000..a2268d62ed --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationFailure.cs @@ -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 +{ + /// + /// for Razor parse failures. + /// + public class RazorCompilationFailure : ICompilationFailure + { + /// Initializes a new instance of . + /// The path of the Razor source file that was compiled. + /// The contents of the Razor source file. + /// A sequence of encountered + /// during compilation. + public RazorCompilationFailure( + [NotNull] string sourceFilePath, + [NotNull] string sourceFileContent, + [NotNull] IEnumerable messages) + { + SourceFilePath = sourceFilePath; + SourceFileContent = sourceFileContent; + Messages = messages; + } + + /// + public string SourceFilePath { get; } + + /// + public string SourceFileContent { get; } + + /// + public string CompiledContent { get; } = null; + + /// + public IEnumerable Messages { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationMessage.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationMessage.cs new file mode 100644 index 0000000000..90fb0a3655 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationMessage.cs @@ -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 +{ + /// + /// for a encountered during parsing. + /// + public class RazorCompilationMessage : ICompilationMessage + { + /// + /// Initializes a with the specified message. + /// + /// A . + /// The path of the Razor source file that was parsed. + 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; + } + + /// + /// Gets a message produced from compilation. + /// + public string Message { get; } + + /// + public int StartColumn { get; } + + /// + public int StartLine { get; } + + /// + public int EndColumn { get; } + + /// + public int EndLine { get; } + + /// + public string SourceFilePath { get; } + + /// + public string FormattedMessage { get; } + + /// + // All Razor diagnostics are errors + public CompilationMessageSeverity Severity { get; } = CompilationMessageSeverity.Error; + + /// + /// Returns a representation of this instance of . + /// + /// A representing this instance. + /// Returns same value as . + public override string ToString() + { + return FormattedMessage; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs index f2fd90a53e..2c7889e848 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs @@ -60,12 +60,12 @@ namespace Microsoft.AspNet.Mvc.Razor } /// - 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 // 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. - var path = fileInfo.PhysicalPath ?? fileInfo.Name; + var path = fileInfo.FileInfo.PhysicalPath ?? fileInfo.RelativePath; var compilationSettings = _compilerOptionsProvider.GetCompilationSettings(_environment); var syntaxTree = SyntaxTreeGenerator.Generate(compilationContent, path, @@ -98,14 +98,15 @@ namespace Microsoft.AspNet.Mvc.Razor 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 - .Where(IsError) - .Select(d => GetCompilationMessage(formatter, d)) - .ToList(); - - return CompilationResult.Failed(fileInfo, compilationContent, messages); + return CompilationResult.Failed(compilationFailure); } Assembly assembly; @@ -215,21 +216,25 @@ namespace Microsoft.AspNet.Mvc.Razor 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) { 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; + } + } } } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs index c6e9045f13..692a9fc7b4 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.Razor } /// - /// Error compiling page at '{0}'. + /// One or more compilation failures occured: /// internal static string CompilationFailed { @@ -35,11 +35,11 @@ namespace Microsoft.AspNet.Mvc.Razor } /// - /// Error compiling page at '{0}'. + /// One or more compilation failures occured: /// - internal static string FormatCompilationFailed(object p0) + internal static string FormatCompilationFailed() { - return string.Format(CultureInfo.CurrentCulture, GetString("CompilationFailed"), p0); + return GetString("CompilationFailed"); } /// diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/RazorCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/RazorCompilationService.cs index 556541c182..526a89e74f 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/RazorCompilationService.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/RazorCompilationService.cs @@ -1,7 +1,9 @@ // 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.IO; using System.Linq; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Razor; using Microsoft.Framework.Internal; @@ -35,16 +37,32 @@ namespace Microsoft.AspNet.Mvc.Razor if (!results.Success) { var messages = results.ParserErrors - .Select(parseError => - new CompilationMessage(parseError.Message, - parseError.Location.CharacterIndex, - parseError.Location.LineIndex, - parseError.Location.CharacterIndex + parseError.Length, - parseError.Location.LineIndex)); - return CompilationResult.Failed(file.FileInfo, results.GeneratedCode, messages); + .Select(parseError => new RazorCompilationMessage(parseError, file.RelativePath)); + var failure = new RazorCompilationFailure( + file.RelativePath, + ReadFileContentsSafely(file.FileInfo), + 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; + } } } } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx index 5aaf28b864..4085f8eda8 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx @@ -121,7 +121,7 @@ Value cannot be null or empty. - Error compiling page at '{0}'. + One or more compilation failures occured: '{0}' cannot be invoked when a Layout page is set to be executed. diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs index c3e36d28e2..d318326a91 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs @@ -1,9 +1,7 @@ // 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.IO; -using System.Text; -using Microsoft.AspNet.FileProviders; +using Microsoft.Framework.Runtime; using Moq; using Xunit; @@ -12,32 +10,18 @@ namespace Microsoft.AspNet.Mvc.Razor.Test public class CompilationResultTest { [Fact] - public void FailedResult_ThrowsWhenAccessingCompiledType() + public void EnsureSuccessful_ThrowsIfCompilationFailed() { // Arrange - var expected = @"Error compiling page at 'myfile'."; - var originalContent = "Original file content"; - var fileInfo = new Mock(); - 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, - "

hello world

", - messages); + var compilationFailure = Mock.Of(); + var result = CompilationResult.Failed(compilationFailure); // Act and Assert - var ex = Assert.Throws(() => result.CompiledType); - Assert.Equal(expected, ex.Message); - var compilationFailure = Assert.Single(ex.CompilationFailures); - Assert.Equal(originalContent, compilationFailure.SourceFileContent); - Assert.Equal(messages, compilationFailure.Messages); + Assert.Null(result.CompiledType); + Assert.Same(compilationFailure, result.CompilationFailure); + var exception = Assert.Throws(() => result.EnsureSuccessful()); + Assert.Collection(exception.CompilationFailures, + failure => Assert.Same(compilationFailure, failure)); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs index 00ba6b46b5..cd0025956b 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs @@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.Razor // Assert Assert.NotSame(CompilerCacheResult.FileNotFound, result); - var actual = result.CompilationResult; + var actual = Assert.IsType(result.CompilationResult); Assert.NotNull(actual); Assert.Same(expected, actual); Assert.Equal("hello world", actual.CompiledContent); @@ -541,23 +541,18 @@ namespace Microsoft.AspNet.Mvc.Razor // Act cache.GetOrAdd("test", _ => uncachedResult); - var result1 = cache.GetOrAdd("test", _ => uncachedResult); - var result2 = cache.GetOrAdd("test", _ => uncachedResult); + var result1 = cache.GetOrAdd("test", _ => { throw new Exception("shouldn't be called."); }); + var result2 = cache.GetOrAdd("test", _ => { throw new Exception("shouldn't be called."); }); // Assert Assert.NotSame(CompilerCacheResult.FileNotFound, result1); Assert.NotSame(CompilerCacheResult.FileNotFound, result2); - var actual1 = result1.CompilationResult; - var actual2 = result2.CompilationResult; + var actual1 = Assert.IsType(result1.CompilationResult); + var actual2 = Assert.IsType(result2.CompilationResult); Assert.NotSame(uncachedResult, actual1); Assert.NotSame(uncachedResult, actual2); - var result = Assert.IsType(actual1); - Assert.Null(actual1.CompiledContent); Assert.Same(type, actual1.CompiledType); - - result = Assert.IsType(actual2); - Assert.Null(actual2.CompiledContent); Assert.Same(type, actual2.CompiledType); } diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs index 35ea01eea7..5b2c57f8ab 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs @@ -31,14 +31,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Test fileInfo.Setup(f => f.PhysicalPath).Returns(viewPath); fileInfo.Setup(f => f.CreateReadStream()).Returns(Stream.Null); + var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml"); + var compiler = new Mock(); - compiler.Setup(c => c.Compile(fileInfo.Object, It.IsAny())) + compiler.Setup(c => c.Compile(relativeFileInfo, It.IsAny())) .Returns(CompilationResult.Successful(typeof(RazorCompilationServiceTest))); var razorService = new RazorCompilationService(compiler.Object, host.Object); - var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml"); - // Act razorService.Compile(relativeFileInfo); @@ -75,9 +75,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var result = razorService.Compile(relativeFileInfo); // Assert - var ex = Assert.Throws(() => result.CompiledType); - var failure = Assert.Single(ex.CompilationFailures); - var message = Assert.Single(failure.Messages); + Assert.NotNull(result.CompilationFailure); + var message = Assert.Single(result.CompilationFailure.Messages); Assert.Equal("some message", message.Message); host.Verify(); } @@ -100,13 +99,13 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var fileInfo = new Mock(); fileInfo.Setup(f => f.CreateReadStream()) .Returns(Stream.Null); + var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml"); var compilationResult = CompilationResult.Successful(typeof(object)); var compiler = new Mock(); - compiler.Setup(c => c.Compile(fileInfo.Object, code)) + compiler.Setup(c => c.Compile(relativeFileInfo, code)) .Returns(compilationResult) .Verifiable(); - var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml"); var razorService = new RazorCompilationService(compiler.Object, host.Object); // Act diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs index 42d078886d..0bf11f625d 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Reflection; using System.Runtime.Versioning; +using Microsoft.AspNet.FileProviders; using Microsoft.Framework.Runtime; using Moq; using Xunit; @@ -37,14 +38,126 @@ public class MyTestType {}"; libraryManager, compilerOptionsProvider.Object, mvcRazorHost.Object); + var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" }, + "some-relative-path"); // Act - var result = compilationService.Compile(new TestFileInfo { PhysicalPath = "SomePath" }, content); + var result = compilationService.Compile(relativeFileInfo, content); // Assert var uncachedResult = Assert.IsType(result); 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(); + compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationBasePath, + applicationEnvironment.RuntimeFramework, + applicationEnvironment.Configuration)) + .Returns(new CompilerOptions()); + var mvcRazorHost = Mock.Of(); + + 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(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(); + compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationBasePath, + applicationEnvironment.RuntimeFramework, + applicationEnvironment.Configuration)) + .Returns(new CompilerOptions()); + var mvcRazorHost = Mock.Of(); + + 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(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(); + compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationBasePath, + applicationEnvironment.RuntimeFramework, + applicationEnvironment.Configuration)) + .Returns(new CompilerOptions()); + var mvcRazorHost = Mock.Of(); + + var compilationService = new RoslynCompilationService(applicationEnvironment, + accessor, + libraryManager, + compilerOptionsProvider.Object, + mvcRazorHost); + var mockFileInfo = new Mock(); + 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(result); + Assert.Null(result.CompiledType); + Assert.Equal("some-relative-path", result.CompilationFailure.SourceFilePath); + Assert.Null(result.CompilationFailure.SourceFileContent); } [Fact] @@ -76,9 +189,11 @@ public class MyNonCustomDefinedClass {} libraryManager, compilerOptionsProvider.Object, mvcRazorHost.Object); + var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" }, + "some-relative-path"); // Act - var result = compilationService.Compile(new TestFileInfo { PhysicalPath = "SomePath" }, content); + var result = compilationService.Compile(relativeFileInfo, content); // Assert Assert.NotNull(result.CompiledType); @@ -111,8 +226,11 @@ public class NotRazorPrefixType {}"; compilerOptionsProvider.Object, mvcRazorHost.Object); + var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" }, + "some-relative-path"); + // Act - var result = compilationService.Compile(new TestFileInfo { PhysicalPath = "SomePath" }, content); + var result = compilationService.Compile(relativeFileInfo, content); // Assert Assert.NotNull(result.CompiledType);