[Fixes #157] ErrorPage middleware doesn't show source code lines of Razor files

This commit is contained in:
Kiran Challa 2015-07-15 19:39:40 -07:00
parent 6e72a3935d
commit 8d583a4718
12 changed files with 666 additions and 59 deletions

View File

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Diagnostics.Views;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
@ -28,6 +29,7 @@ namespace Microsoft.AspNet.Diagnostics
private readonly ErrorPageOptions _options;
private static readonly bool IsMono = Type.GetType("Mono.Runtime") != null;
private readonly ILogger _logger;
private readonly IFileProvider _fileProvider;
/// <summary>
/// Initializes a new instance of the <see cref="ErrorPageMiddleware"/> class
@ -35,11 +37,15 @@ namespace Microsoft.AspNet.Diagnostics
/// <param name="next"></param>
/// <param name="options"></param>
public ErrorPageMiddleware(
[NotNull] RequestDelegate next, [NotNull] ErrorPageOptions options, ILoggerFactory loggerFactory)
[NotNull] RequestDelegate next,
[NotNull] ErrorPageOptions options,
ILoggerFactory loggerFactory,
IApplicationEnvironment appEnvironment)
{
_next = next;
_options = options;
_logger = loggerFactory.CreateLogger<ErrorPageMiddleware>();
_fileProvider = options.FileProvider ?? new PhysicalFileProvider(appEnvironment.ApplicationBasePath);
}
/// <summary>
@ -183,31 +189,102 @@ namespace Microsoft.AspNet.Diagnostics
int lineNumber = line.ToInt32();
return string.IsNullOrEmpty(file)
? LoadFrame(string.IsNullOrEmpty(function) ? line.ToString() : function, string.Empty, 0)
: LoadFrame(function, file, lineNumber);
if (string.IsNullOrEmpty(file))
{
return GetStackFrame(
// Handle stack trace lines like
// "--- End of stack trace from previous location where exception from thrown ---"
string.IsNullOrEmpty(function) ? line.ToString() : function,
file: string.Empty,
lineNumber: 0);
}
else
{
return GetStackFrame(function, file, lineNumber);
}
}
private StackFrame LoadFrame(string function, string file, int lineNumber)
// make it internal to enable unit testing
internal StackFrame GetStackFrame(string function, string file, int lineNumber)
{
var frame = new StackFrame { Function = function, File = file, Line = lineNumber };
if (string.IsNullOrEmpty(file))
{
return frame;
}
IEnumerable<string> lines = null;
if (File.Exists(file))
{
var code = File.ReadLines(file);
ReadFrameContent(frame, code, lineNumber, lineNumber);
lines = File.ReadLines(file);
}
else
{
// Handle relative paths and embedded files
var fileInfo = _fileProvider.GetFileInfo(file);
if (fileInfo.Exists)
{
// ReadLines doesn't accept a stream. Use ReadLines as its more efficient
// relative to reading lines via stream reader
if (!string.IsNullOrEmpty(fileInfo.PhysicalPath))
{
lines = File.ReadLines(fileInfo.PhysicalPath);
}
else
{
lines = ReadLines(fileInfo);
}
}
}
if (lines != null)
{
ReadFrameContent(frame, lines, lineNumber, lineNumber);
}
return frame;
}
private void ReadFrameContent(StackFrame frame,
IEnumerable<string> code,
int startLineNumber,
int endLineNumber)
// make it internal to enable unit testing
internal void ReadFrameContent(
StackFrame frame,
IEnumerable<string> allLines,
int errorStartLineNumberInFile,
int errorEndLineNumberInFile)
{
frame.PreContextLine = Math.Max(startLineNumber - _options.SourceCodeLineCount, 1);
frame.PreContextCode = code.Skip(frame.PreContextLine - 1).Take(startLineNumber - frame.PreContextLine).ToArray();
frame.ContextCode = code.Skip(startLineNumber - 1).Take(1 + Math.Max(0, endLineNumber - startLineNumber));
frame.PostContextCode = code.Skip(startLineNumber).Take(_options.SourceCodeLineCount).ToArray();
// Get the line boundaries in the file to be read and read all these lines at once into an array.
var preErrorLineNumberInFile = Math.Max(errorStartLineNumberInFile - _options.SourceCodeLineCount, 1);
var postErrorLineNumberInFile = errorEndLineNumberInFile + _options.SourceCodeLineCount;
var codeBlock = allLines
.Skip(preErrorLineNumberInFile - 1)
.Take(postErrorLineNumberInFile - preErrorLineNumberInFile + 1)
.ToArray();
var numOfErrorLines = (errorEndLineNumberInFile - errorStartLineNumberInFile) + 1;
var errorStartLineNumberInArray = errorStartLineNumberInFile - preErrorLineNumberInFile;
frame.PreContextLine = preErrorLineNumberInFile;
frame.PreContextCode = codeBlock.Take(errorStartLineNumberInArray).ToArray();
frame.ContextCode = codeBlock
.Skip(errorStartLineNumberInArray)
.Take(numOfErrorLines)
.ToArray();
frame.PostContextCode = codeBlock
.Skip(errorStartLineNumberInArray + numOfErrorLines)
.ToArray();
}
private static IEnumerable<string> ReadLines(IFileInfo fileInfo)
{
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
internal class Chunk

View File

@ -2,6 +2,8 @@
// 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.Diagnostics
{
/// <summary>
@ -23,5 +25,13 @@ namespace Microsoft.AspNet.Diagnostics
/// source code referenced by the exception stack trace is present on the server.
/// </summary>
public int SourceCodeLineCount { get; set; }
/// <summary>
/// Provides files containing source code used to display contextual information of an exception.
/// </summary>
/// <remarks>
/// If <c>null</c> <see cref="ErrorPageMiddleware" /> will use a <see cref="PhysicalFileProvider"/>.
/// </remarks>
public IFileProvider FileProvider { get; set; }
}
}

View File

@ -143,8 +143,8 @@ using Views
#line hidden
WriteLiteral(" <li class=\"frame\"");
WriteAttribute("tabindex", Tuple.Create(" tabindex=\"", 1231), Tuple.Create("\"", 1251),
Tuple.Create(Tuple.Create("", 1242), Tuple.Create<System.Object, System.Int32>(tabIndex, 1242), false));
WriteAttribute("tabindex", Tuple.Create(" tabindex=\"", 1246), Tuple.Create("\"", 1266),
Tuple.Create(Tuple.Create("", 1257), Tuple.Create<System.Object, System.Int32>(tabIndex, 1257), false));
WriteLiteral(">\r\n");
#line 42 "CompilationErrorPage.cshtml"
@ -193,7 +193,7 @@ using Views
#line hidden
#line 48 "CompilationErrorPage.cshtml"
if (frame.Line != 0 && frame.ContextCode !=null && frame.ContextCode.Any())
if (frame.Line != 0 && frame.ContextCode.Any())
{
#line default
@ -207,15 +207,15 @@ using Views
#line hidden
#line 51 "CompilationErrorPage.cshtml"
if (frame.PreContextCode != null)
if (frame.PreContextCode.Any())
{
#line default
#line hidden
WriteLiteral(" <ol");
WriteAttribute("start", Tuple.Create(" start=\"", 1777), Tuple.Create("\"", 1806),
Tuple.Create(Tuple.Create("", 1785), Tuple.Create<System.Object, System.Int32>(frame.PreContextLine, 1785), false));
WriteAttribute("start", Tuple.Create(" start=\"", 1790), Tuple.Create("\"", 1819),
Tuple.Create(Tuple.Create("", 1798), Tuple.Create<System.Object, System.Int32>(frame.PreContextLine, 1798), false));
WriteLiteral(" class=\"collapsible\">\r\n");
#line 54 "CompilationErrorPage.cshtml"
@ -251,8 +251,8 @@ using Views
#line hidden
WriteLiteral(" <ol");
WriteAttribute("start", Tuple.Create(" start=\"", 2187), Tuple.Create("\"", 2206),
Tuple.Create(Tuple.Create("", 2195), Tuple.Create<System.Object, System.Int32>(frame.Line, 2195), false));
WriteAttribute("start", Tuple.Create(" start=\"", 2200), Tuple.Create("\"", 2219),
Tuple.Create(Tuple.Create("", 2208), Tuple.Create<System.Object, System.Int32>(frame.Line, 2208), false));
WriteLiteral(" class=\"highlight\">\r\n");
#line 61 "CompilationErrorPage.cshtml"
@ -288,15 +288,15 @@ using Views
#line hidden
#line 66 "CompilationErrorPage.cshtml"
if (frame.PostContextCode != null)
if (frame.PostContextCode.Any())
{
#line default
#line hidden
WriteLiteral(" <ol");
WriteAttribute("start", Tuple.Create(" start=\'", 2635), Tuple.Create("\'", 2660),
Tuple.Create(Tuple.Create("", 2643), Tuple.Create<System.Object, System.Int32>(frame.Line + 1, 2643), false));
WriteAttribute("start", Tuple.Create(" start=\'", 2646), Tuple.Create("\'", 2671),
Tuple.Create(Tuple.Create("", 2654), Tuple.Create<System.Object, System.Int32>(frame.Line + 1, 2654), false));
WriteLiteral(" class=\"collapsible\">\r\n");
#line 69 "CompilationErrorPage.cshtml"

View File

@ -45,10 +45,10 @@
<h3>@frame.ErrorDetails</h3>
}
@if (frame.Line != 0 && frame.ContextCode !=null && frame.ContextCode.Any())
@if (frame.Line != 0 && frame.ContextCode.Any())
{
<div class="source">
@if (frame.PreContextCode != null)
@if (frame.PreContextCode.Any())
{
<ol start="@frame.PreContextLine" class="collapsible">
@foreach (var line in frame.PreContextCode)
@ -63,7 +63,7 @@
<li><span>@line</span></li>
}
</ol>
@if (frame.PostContextCode != null)
@if (frame.PostContextCode.Any())
{
<ol start='@(frame.Line + 1)' class="collapsible">
@foreach (var line in frame.PostContextCode)

View File

@ -65,8 +65,8 @@ using Views
#line hidden
WriteLiteral("\r\n<!DOCTYPE html>\r\n<html");
WriteAttribute("lang", Tuple.Create(" lang=\"", 518), Tuple.Create("\"", 579),
Tuple.Create(Tuple.Create("", 525), Tuple.Create<System.Object, System.Int32>(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, 525), false));
WriteAttribute("lang", Tuple.Create(" lang=\"", 533), Tuple.Create("\"", 594),
Tuple.Create(Tuple.Create("", 540), Tuple.Create<System.Object, System.Int32>(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, 540), false));
WriteLiteral(" xmlns=\"http://www.w3.org/1999/xhtml\">\r\n <head>\r\n <meta charset=\"utf-8\"" +
" />\r\n <title>");
#line 26 "ErrorPage.cshtml"
@ -147,8 +147,8 @@ using Views
#line default
#line hidden
WriteLiteral(" in <code");
WriteAttribute("title", Tuple.Create(" title=\"", 1895), Tuple.Create("\"", 1919),
Tuple.Create(Tuple.Create("", 1903), Tuple.Create<System.Object, System.Int32>(firstFrame.File, 1903), false));
WriteAttribute("title", Tuple.Create(" title=\"", 1910), Tuple.Create("\"", 1934),
Tuple.Create(Tuple.Create("", 1918), Tuple.Create<System.Object, System.Int32>(firstFrame.File, 1918), false));
WriteLiteral(">");
#line 50 "ErrorPage.cshtml"
Write(System.IO.Path.GetFileName(firstFrame.File));
@ -281,8 +281,8 @@ using Views
#line hidden
WriteLiteral(" <li class=\"frame\"");
WriteAttribute("tabindex", Tuple.Create(" tabindex=\"", 3327), Tuple.Create("\"", 3347),
Tuple.Create(Tuple.Create("", 3338), Tuple.Create<System.Object, System.Int32>(tabIndex, 3338), false));
WriteAttribute("tabindex", Tuple.Create(" tabindex=\"", 3342), Tuple.Create("\"", 3362),
Tuple.Create(Tuple.Create("", 3353), Tuple.Create<System.Object, System.Int32>(tabIndex, 3353), false));
WriteLiteral(">\r\n");
#line 87 "ErrorPage.cshtml"
@ -332,8 +332,8 @@ using Views
#line default
#line hidden
WriteLiteral(" in <code");
WriteAttribute("title", Tuple.Create(" title=\"", 3742), Tuple.Create("\"", 3761),
Tuple.Create(Tuple.Create("", 3750), Tuple.Create<System.Object, System.Int32>(frame.File, 3750), false));
WriteAttribute("title", Tuple.Create(" title=\"", 3757), Tuple.Create("\"", 3776),
Tuple.Create(Tuple.Create("", 3765), Tuple.Create<System.Object, System.Int32>(frame.File, 3765), false));
WriteLiteral(">");
#line 94 "ErrorPage.cshtml"
Write(System.IO.Path.GetFileName(frame.File));
@ -355,7 +355,7 @@ using Views
#line hidden
#line 97 "ErrorPage.cshtml"
if (frame.Line != 0 && frame.ContextCode !=null && frame.ContextCode.Any())
if (frame.Line != 0 && frame.ContextCode.Any())
{
#line default
@ -369,15 +369,15 @@ using Views
#line hidden
#line 100 "ErrorPage.cshtml"
if (frame.PreContextCode != null)
if (frame.PreContextCode.Any())
{
#line default
#line hidden
WriteLiteral(" <ol");
WriteAttribute("start", Tuple.Create(" start=\"", 4194), Tuple.Create("\"", 4223),
Tuple.Create(Tuple.Create("", 4202), Tuple.Create<System.Object, System.Int32>(frame.PreContextLine, 4202), false));
WriteAttribute("start", Tuple.Create(" start=\"", 4207), Tuple.Create("\"", 4236),
Tuple.Create(Tuple.Create("", 4215), Tuple.Create<System.Object, System.Int32>(frame.PreContextLine, 4215), false));
WriteLiteral(" class=\"collapsible\">\r\n");
#line 103 "ErrorPage.cshtml"
@ -413,8 +413,8 @@ using Views
#line hidden
WriteLiteral("\r\n <ol");
WriteAttribute("start", Tuple.Create(" start=\"", 4663), Tuple.Create("\"", 4682),
Tuple.Create(Tuple.Create("", 4671), Tuple.Create<System.Object, System.Int32>(frame.Line, 4671), false));
WriteAttribute("start", Tuple.Create(" start=\"", 4676), Tuple.Create("\"", 4695),
Tuple.Create(Tuple.Create("", 4684), Tuple.Create<System.Object, System.Int32>(frame.Line, 4684), false));
WriteLiteral(" class=\"highlight\">\r\n");
#line 111 "ErrorPage.cshtml"
@ -450,15 +450,15 @@ using Views
#line hidden
#line 117 "ErrorPage.cshtml"
if (frame.PostContextCode != null)
if (frame.PostContextCode.Any())
{
#line default
#line hidden
WriteLiteral(" <ol");
WriteAttribute("start", Tuple.Create(" start=\'", 5177), Tuple.Create("\'", 5202),
Tuple.Create(Tuple.Create("", 5185), Tuple.Create<System.Object, System.Int32>(frame.Line + 1, 5185), false));
WriteAttribute("start", Tuple.Create(" start=\'", 5188), Tuple.Create("\'", 5213),
Tuple.Create(Tuple.Create("", 5196), Tuple.Create<System.Object, System.Int32>(frame.Line + 1, 5196), false));
WriteLiteral(" class=\"collapsible\">\r\n");
#line 120 "ErrorPage.cshtml"

View File

@ -94,10 +94,10 @@
<h3>@frame.Function in <code title="@frame.File">@System.IO.Path.GetFileName(frame.File)</code></h3>
}
@if (frame.Line != 0 && frame.ContextCode !=null && frame.ContextCode.Any())
@if (frame.Line != 0 && frame.ContextCode.Any())
{
<div class="source">
@if (frame.PreContextCode != null)
@if (frame.PreContextCode.Any())
{
<ol start="@frame.PreContextLine" class="collapsible">
@foreach (var line in frame.PreContextCode)
@ -114,7 +114,7 @@
}
</ol>
@if (frame.PostContextCode != null)
@if (frame.PostContextCode.Any())
{
<ol start='@(frame.Line + 1)' class="collapsible">
@foreach (var line in frame.PostContextCode)

View File

@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNet.Diagnostics.Views
{
@ -32,19 +33,19 @@ namespace Microsoft.AspNet.Diagnostics.Views
public int PreContextLine { get; set; }
/// <summary>
///
/// Lines of code before the actual error line(s).
/// </summary>
public IEnumerable<string> PreContextCode { get; set; }
public IEnumerable<string> PreContextCode { get; set; } = Enumerable.Empty<string>();
/// <summary>
///
/// Line(s) of code responsible for the error.
/// </summary>
public IEnumerable<string> ContextCode { get; set; }
public IEnumerable<string> ContextCode { get; set; } = Enumerable.Empty<string>();
/// <summary>
///
/// Lines of code after the actual error line(s).
/// </summary>
public IEnumerable<string> PostContextCode { get; set; }
public IEnumerable<string> PostContextCode { get; set; } = Enumerable.Empty<string>();
/// <summary>
/// Specific error details for this stack frame.

View File

@ -7,13 +7,17 @@
},
"dependencies": {
"Microsoft.AspNet.Diagnostics.Abstractions": "1.0.0-*",
"Microsoft.AspNet.FileProviders.Physical": "1.0.0-*",
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
"Microsoft.AspNet.WebUtilities": "1.0.0-*",
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*",
"Microsoft.Framework.NotNullAttribute.Sources": {
"type": "build",
"version": "1.0.0-*"
},
"Microsoft.Framework.OptionsModel": "1.0.0-*",
"Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" },
"Microsoft.Framework.WebEncoders.Core": "1.0.0-*",
"Microsoft.Framework.Runtime.Compilation.Abstractions": "1.0.0-*",
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*"
"Microsoft.Framework.WebEncoders.Core": "1.0.0-*"
},
"frameworks": {
"dnx451": {},

View File

@ -0,0 +1,451 @@
// 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.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Diagnostics.Views;
using Microsoft.AspNet.FileProviders;
using Microsoft.Framework.Caching;
using Microsoft.Framework.Logging;
using Microsoft.Framework.Runtime;
using Xunit;
namespace Microsoft.AspNet.Diagnostics
{
public class ErrorPageMiddlewareTest
{
[Theory]
[InlineData("TestFiles/SourceFile.txt")]
[InlineData(@"TestFiles\SourceFile.txt")]
public void UsesDefaultFileProvider_IfNotProvidedOnOptions(string relativePath)
{
// Arrange & Act
var middleware = GetErrorPageMiddleware(fileProvider: null);
var stackFrame = middleware.GetStackFrame("func1", relativePath, lineNumber: 10);
// Assert
// Lines 4-16 (inclusive) is the code block
Assert.Equal(4, stackFrame.PreContextLine);
Assert.Equal(GetCodeLines(4, 9), stackFrame.PreContextCode);
Assert.Equal(GetCodeLines(10, 10), stackFrame.ContextCode);
Assert.Equal(GetCodeLines(11, 16), stackFrame.PostContextCode);
}
public static TheoryData<string> AbsolutePathsData
{
get
{
var rootPath = Directory.GetCurrentDirectory();
return new TheoryData<string>()
{
Path.Combine(rootPath, "TestFiles/SourceFile.txt"),
Path.Combine(rootPath, @"TestFiles\SourceFile.txt")
};
}
}
[Theory]
[MemberData(nameof(AbsolutePathsData))]
public void DisplaysSourceCodeLines_ForAbsolutePaths(string absoluteFilePath)
{
// Arrange
var rootPath = Directory.GetCurrentDirectory();
// PhysicalFileProvider handles only relative paths but we fall back to work with absolute paths too
var provider = new PhysicalFileProvider(rootPath);
// Act
var middleware = GetErrorPageMiddleware(provider);
var stackFrame = middleware.GetStackFrame("func1", absoluteFilePath, lineNumber: 10);
// Assert
// Lines 4-16 (inclusive) is the code block
Assert.Equal(4, stackFrame.PreContextLine);
Assert.Equal(GetCodeLines(4, 9), stackFrame.PreContextCode);
Assert.Equal(GetCodeLines(10, 10), stackFrame.ContextCode);
Assert.Equal(GetCodeLines(11, 16), stackFrame.PostContextCode);
}
[Theory]
[InlineData("TestFiles/SourceFile.txt")]
[InlineData(@"TestFiles\SourceFile.txt")]
public void DisplaysSourceCodeLines_ForRelativePaths(string relativePath)
{
// Arrange
var rootPath = Directory.GetCurrentDirectory();
var provider = new PhysicalFileProvider(rootPath);
// Act
var middleware = GetErrorPageMiddleware(provider);
var stackFrame = middleware.GetStackFrame("func1", relativePath, lineNumber: 10);
// Assert
// Lines 4-16 (inclusive) is the code block
Assert.Equal(4, stackFrame.PreContextLine);
Assert.Equal(GetCodeLines(4, 9), stackFrame.PreContextCode);
Assert.Equal(GetCodeLines(10, 10), stackFrame.ContextCode);
Assert.Equal(GetCodeLines(11, 16), stackFrame.PostContextCode);
}
[Theory]
[InlineData("TestFiles/EmbeddedSourceFile.txt")]
//[InlineData(@"TestFiles\EmbeddedSourceFile.txt")]
public void DisplaysSourceCodeLines_ForRelativeEmbeddedPaths(string relativePath)
{
// Arrange
var provider = new EmbeddedFileProvider(
GetType().GetTypeInfo().Assembly,
baseNamespace: $"{typeof(ErrorPageMiddlewareTest).GetTypeInfo().Assembly.GetName().Name}.Resources");
// Act
var middleware = GetErrorPageMiddleware(provider);
var stackFrame = middleware.GetStackFrame("func1", relativePath, lineNumber: 10);
// Assert
// Lines 4-16 (inclusive) is the code block
Assert.Equal(4, stackFrame.PreContextLine);
Assert.Equal(GetCodeLines(4, 9), stackFrame.PreContextCode);
Assert.Equal(GetCodeLines(10, 10), stackFrame.ContextCode);
Assert.Equal(GetCodeLines(11, 16), stackFrame.PostContextCode);
}
public static TheoryData<ErrorData> DisplaysSourceCodeLines_PreAndPostErrorLineData
{
get
{
return new TheoryData<ErrorData>()
{
new ErrorData()
{
AllLines = GetCodeLines(1, 30),
ErrorStartLine = 10,
ErrorEndLine = 10,
ExpectedPreContextLine = 4,
ExpectedPreErrorCode = GetCodeLines(4, 9),
ExpectedErrorCode = GetCodeLines(10, 10),
ExpectedPostErrorCode = GetCodeLines(11, 16)
},
new ErrorData()
{
AllLines = GetCodeLines(1, 30),
ErrorStartLine = 10,
ErrorEndLine = 13, // multi-line error
ExpectedPreContextLine = 4,
ExpectedPreErrorCode = GetCodeLines(4, 9),
ExpectedErrorCode = GetCodeLines(10, 13),
ExpectedPostErrorCode = GetCodeLines(14, 19)
},
// PreErrorCode less than source code line count
new ErrorData()
{
AllLines = GetCodeLines(1, 10),
ErrorStartLine = 1,
ErrorEndLine = 1,
ExpectedPreContextLine = 1,
ExpectedPreErrorCode = Enumerable.Empty<string>(),
ExpectedErrorCode = GetCodeLines(1, 1),
ExpectedPostErrorCode = GetCodeLines(2, 7)
},
new ErrorData()
{
AllLines = GetCodeLines(1, 10),
ErrorStartLine = 3,
ErrorEndLine = 5,
ExpectedPreContextLine = 1,
ExpectedPreErrorCode = GetCodeLines(1, 2),
ExpectedErrorCode = GetCodeLines(3, 5),
ExpectedPostErrorCode = GetCodeLines(6, 10)
},
// PostErrorCode less than source code line count
new ErrorData()
{
AllLines = GetCodeLines(1, 10),
ErrorStartLine = 10,
ErrorEndLine = 10,
ExpectedPreContextLine = 4,
ExpectedPreErrorCode = GetCodeLines(4, 9),
ExpectedErrorCode = GetCodeLines(10, 10),
ExpectedPostErrorCode = Enumerable.Empty<string>()
},
new ErrorData()
{
AllLines = GetCodeLines(1, 10),
ErrorStartLine = 7,
ErrorEndLine = 10,
ExpectedPreContextLine = 1,
ExpectedPreErrorCode = GetCodeLines(1, 6),
ExpectedErrorCode = GetCodeLines(7, 10),
ExpectedPostErrorCode = Enumerable.Empty<string>()
},
new ErrorData()
{
AllLines = GetCodeLines(1, 10),
ErrorStartLine = 5,
ErrorEndLine = 8,
ExpectedPreContextLine = 1,
ExpectedPreErrorCode = GetCodeLines(1, 4),
ExpectedErrorCode = GetCodeLines(5, 8),
ExpectedPostErrorCode = GetCodeLines(9, 10)
},
// Pre and Post error code less than source code line count
new ErrorData()
{
AllLines = GetCodeLines(1, 4),
ErrorStartLine = 2,
ErrorEndLine = 3,
ExpectedPreContextLine = 1,
ExpectedPreErrorCode = GetCodeLines(1, 1),
ExpectedErrorCode = GetCodeLines(2, 3),
ExpectedPostErrorCode = GetCodeLines(4, 4)
},
new ErrorData()
{
AllLines = GetCodeLines(1, 4),
ErrorStartLine = 1,
ErrorEndLine = 4,
ExpectedPreContextLine = 1,
ExpectedPreErrorCode = Enumerable.Empty<string>(),
ExpectedErrorCode = GetCodeLines(1, 4),
ExpectedPostErrorCode = Enumerable.Empty<string>()
},
// change source code line count
new ErrorData()
{
SourceCodeLineCount = 1,
AllLines = GetCodeLines(1, 1),
ErrorStartLine = 1,
ErrorEndLine = 1,
ExpectedPreContextLine = 1,
ExpectedPreErrorCode = Enumerable.Empty<string>(),
ExpectedErrorCode = GetCodeLines(1, 1),
ExpectedPostErrorCode = Enumerable.Empty<string>()
},
};
}
}
[Theory]
[MemberData(nameof(DisplaysSourceCodeLines_PreAndPostErrorLineData))]
public void DisplaysSourceCodeLines_PreAndPostErrorLine(ErrorData errorData)
{
// Arrange
var middleware = GetErrorPageMiddleware();
var stackFrame = new StackFrame();
// Act
middleware.ReadFrameContent(
stackFrame, errorData.AllLines, errorData.ErrorStartLine, errorData.ErrorEndLine);
// Assert
Assert.Equal(errorData.ExpectedPreContextLine, stackFrame.PreContextLine);
Assert.Equal(errorData.ExpectedPreErrorCode, stackFrame.PreContextCode);
Assert.Equal(errorData.ExpectedErrorCode, stackFrame.ContextCode);
Assert.Equal(errorData.ExpectedPostErrorCode, stackFrame.PostContextCode);
}
private static IEnumerable<string> GetCodeLines(int fromLine, int toLine)
{
var start = fromLine;
var count = toLine - fromLine + 1;
return Enumerable.Range(start, count).Select(i => string.Format("Line{0}", i));
}
private ErrorPageMiddleware GetErrorPageMiddleware(
IFileProvider fileProvider = null, int sourceCodeLineCount = 6)
{
var errorPageOptions = new ErrorPageOptions();
errorPageOptions.SourceCodeLineCount = sourceCodeLineCount;
if (fileProvider != null)
{
errorPageOptions.FileProvider = fileProvider;
}
var middleware = new ErrorPageMiddleware(
(httpContext) => { return Task.FromResult(0); },
errorPageOptions,
new LoggerFactory(),
new TestApplicationEnvironment());
return middleware;
}
private class TestApplicationEnvironment : IApplicationEnvironment
{
public string ApplicationBasePath
{
get
{
return Directory.GetCurrentDirectory();
}
}
public string ApplicationName
{
get
{
throw new NotImplementedException();
}
}
public string ApplicationVersion
{
get
{
throw new NotImplementedException();
}
}
public string Configuration
{
get
{
throw new NotImplementedException();
}
}
public FrameworkName RuntimeFramework
{
get
{
throw new NotImplementedException();
}
}
public string Version
{
get
{
throw new NotImplementedException();
}
}
public object GetData(string name)
{
throw new NotImplementedException();
}
public void SetData(string name, object value)
{
throw new NotImplementedException();
}
}
private class TestFileProvider : IFileProvider
{
private readonly IEnumerable<string> _sourceCodeLines;
public TestFileProvider(IEnumerable<string> sourceCodeLines)
{
_sourceCodeLines = sourceCodeLines;
}
public IDirectoryContents GetDirectoryContents(string subpath)
{
throw new NotImplementedException();
}
public IFileInfo GetFileInfo(string subpath)
{
return new TestFileInfo(_sourceCodeLines);
}
public IExpirationTrigger Watch(string filter)
{
throw new NotImplementedException();
}
}
private class TestFileInfo : IFileInfo
{
private readonly MemoryStream _stream;
public TestFileInfo(IEnumerable<string> sourceCodeLines)
{
_stream = new MemoryStream();
using (var writer = new StreamWriter(_stream, Encoding.UTF8, 1024, leaveOpen: true))
{
foreach (var line in sourceCodeLines)
{
writer.WriteLine(line);
}
}
_stream.Seek(0, SeekOrigin.Begin);
}
public bool Exists
{
get
{
return true;
}
}
public bool IsDirectory
{
get
{
throw new NotImplementedException();
}
}
public DateTimeOffset LastModified
{
get
{
throw new NotImplementedException();
}
}
public long Length
{
get
{
throw new NotImplementedException();
}
}
public string Name
{
get
{
throw new NotImplementedException();
}
}
public string PhysicalPath
{
get
{
return null;
}
}
public Stream CreateReadStream()
{
return _stream;
}
}
public class ErrorData
{
public int SourceCodeLineCount { get; set; } = 6;
public IEnumerable<string> AllLines { get; set; }
public int ErrorStartLine { get; set; }
public int ErrorEndLine { get; set; }
public int ExpectedPreContextLine { get; set; }
public IEnumerable<string> ExpectedPreErrorCode { get; set; }
public IEnumerable<string> ExpectedErrorCode { get; set; }
public IEnumerable<string> ExpectedPostErrorCode { get; set; }
}
}
}

View File

@ -0,0 +1,30 @@
Line1
Line2
Line3
Line4
Line5
Line6
Line7
Line8
Line9
Line10
Line11
Line12
Line13
Line14
Line15
Line16
Line17
Line18
Line19
Line20
Line21
Line22
Line23
Line24
Line25
Line26
Line27
Line28
Line29
Line30

View File

@ -0,0 +1,30 @@
Line1
Line2
Line3
Line4
Line5
Line6
Line7
Line8
Line9
Line10
Line11
Line12
Line13
Line14
Line15
Line16
Line17
Line18
Line19
Line20
Line21
Line22
Line23
Line24
Line25
Line26
Line27
Line28
Line29
Line30

View File

@ -4,6 +4,7 @@
},
"dependencies": {
"Microsoft.AspNet.Diagnostics.Elm": "1.0.0-*",
"Microsoft.AspNet.FileProviders.Embedded": "1.0.0-*",
"Microsoft.AspNet.TestHost": "1.0.0-*",
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
"xunit.runner.aspnet": "2.0.0-aspnet-*"
@ -18,7 +19,10 @@
"dnxcore50": {
}
},
"commands": {
"test": "xunit.runner.aspnet"
}
},
"resource": [ "Resources/**" ]
}